]> www.fi.muni.cz Git - evince.git/blob - pdf/xpdf/pdf-document.cc
Implement "Save a copy..." menu item
[evince.git] / pdf / xpdf / pdf-document.cc
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; c-indent-level: 8 -*- */
2 /* pdfdocument.h: Implementation of EvDocument for PDF
3  * Copyright (C) 2004, Red Hat, Inc.
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2, or (at your option)
8  * any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18  */
19
20 #include <glib/gi18n.h>
21
22 #include "gpdf-g-switch.h"
23 #include "pdf-document.h"
24 #include "ev-ps-exporter.h"
25 #include "ev-document-find.h"
26 #include "gpdf-g-switch.h"
27 #include "ev-document-bookmarks.h"
28 #include "ev-document-misc.h"
29 #include "ev-document-thumbnails.h"
30
31 #include "GlobalParams.h"
32 #include "GDKSplashOutputDev.h"
33 #include "PDFDoc.h"
34 #include "Outline.h"
35 #include "UnicodeMap.h"
36 #include "GlobalParams.h"
37 #include "GfxState.h"
38 #include "Thumb.h"
39 #include "goo/GList.h"
40 #include "PSOutputDev.h"
41
42 enum {
43         PROP_0,
44         PROP_TITLE
45 };
46
47 typedef struct
48 {
49         PdfDocument *document;
50         gunichar *ucs4;
51         glong ucs4_len;
52         guint idle;
53         /* full results are only possible for the rendered current page */
54         int current_page;
55         GArray *current_page_results;
56         guchar *other_page_flags; /* length n_pages + 1, first element ignored */
57         int start_page;   /* skip this one as we iterate, since we did it first */
58         int search_page;  /* the page we're searching now */
59         TextOutputDev *output_dev;
60 } PdfDocumentSearch;
61
62 typedef struct _PdfDocumentClass PdfDocumentClass;
63
64 #define PDF_DOCUMENT_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST ((klass), PDF_TYPE_DOCUMENT, PdfDocumentClass))
65 #define PDF_IS_DOCUMENT_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE ((klass), PDF_TYPE_DOCUMENT))
66 #define PDF_DOCUMENT_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS ((obj), PDF_TYPE_DOCUMENT, PdfDocumentClass))
67
68 struct _PdfDocumentClass
69 {
70         GObjectClass parent_class;
71 };
72
73 struct _PdfDocument
74 {
75         GObject parent_instance;
76
77         int page;
78         int page_x_offset;
79         int page_y_offset;
80         double scale;
81         GdkDrawable *target;
82
83         GDKSplashOutputDev *out;
84         PSOutputDev *ps_out;
85         PDFDoc *doc;
86         UnicodeMap *umap;
87
88         gboolean page_valid;
89
90         PdfDocumentSearch *search;
91 };
92
93 static void pdf_document_document_bookmarks_iface_init  (EvDocumentBookmarksIface  *iface);
94 static void pdf_document_document_thumbnails_iface_init (EvDocumentThumbnailsIface *iface);
95 static void pdf_document_document_iface_init            (EvDocumentIface           *iface);
96 static void pdf_document_ps_exporter_iface_init (EvPSExporterIface   *iface);
97 static void pdf_document_find_iface_init        (EvDocumentFindIface *iface);
98 static void pdf_document_search_free            (PdfDocumentSearch   *search);
99 static void pdf_document_search_page_changed    (PdfDocumentSearch   *search);
100
101 G_DEFINE_TYPE_WITH_CODE (PdfDocument, pdf_document, G_TYPE_OBJECT,
102                          {
103                                  G_IMPLEMENT_INTERFACE (EV_TYPE_DOCUMENT,
104                                                         pdf_document_document_iface_init);
105                                  G_IMPLEMENT_INTERFACE (EV_TYPE_DOCUMENT_BOOKMARKS,
106                                                         pdf_document_document_bookmarks_iface_init);
107                                  G_IMPLEMENT_INTERFACE (EV_TYPE_DOCUMENT_THUMBNAILS,
108                                                         pdf_document_document_thumbnails_iface_init);
109                                  G_IMPLEMENT_INTERFACE (EV_TYPE_PS_EXPORTER,
110                                                         pdf_document_ps_exporter_iface_init);
111                                  G_IMPLEMENT_INTERFACE (EV_TYPE_DOCUMENT_FIND,
112                                                         pdf_document_find_iface_init);
113                          });
114
115 static gboolean
116 document_validate_page (PdfDocument *pdf_document)
117 {
118         if (!pdf_document->page_valid) {
119                 pdf_document->doc->displayPage (pdf_document->out, pdf_document->page,
120                                                 72 * pdf_document->scale,
121                                                 72 * pdf_document->scale,
122                                                 0, gTrue, gTrue);
123
124                 pdf_document->page_valid = TRUE;
125
126                 /* Update the search results available to the app since
127                  * we only provide full results on the current page
128                  */
129                 if (pdf_document->search)
130                         pdf_document_search_page_changed (pdf_document->search);
131         }
132
133         return pdf_document->page_valid;
134 }
135
136 static gboolean
137 pdf_document_load (EvDocument  *document,
138                    const char  *uri,
139                    GError     **error)
140 {
141         PdfDocument *pdf_document = PDF_DOCUMENT (document);
142         PDFDoc *newDoc;
143         int err;
144         char *filename;
145         GString *filename_g;
146         GString *enc;
147
148         if (!globalParams) {
149                 globalParams = new GlobalParams("/etc/xpdfrc");
150                 globalParams->setupBaseFontsFc(NULL);
151         }
152
153         if (! pdf_document->umap) {
154                 enc = new GString("UTF-8");
155                 pdf_document->umap = globalParams->getUnicodeMap(enc);
156                 pdf_document->umap->incRefCnt ();
157                 delete enc;
158         }
159
160         filename = g_filename_from_uri (uri, NULL, error);
161         if (!filename)
162                 return FALSE;
163
164         filename_g = new GString (filename);
165         g_free (filename);
166
167         // open the PDF file, assumes ownership of filename_g
168         newDoc = new PDFDoc(filename_g, 0, 0);
169
170         if (!newDoc->isOk()) {
171                 err = newDoc->getErrorCode();
172                 delete newDoc;
173
174                 /* FIXME: Add a real error enum to EvDocument */
175                 g_set_error (error, G_FILE_ERROR,
176                              G_FILE_ERROR_FAILED,
177                              "Failed to load document (error %d) '%s'\n",
178                              err,
179                              uri);
180
181                 return FALSE;
182         }
183
184         if (pdf_document->doc)
185                 delete pdf_document->doc;
186         pdf_document->doc = newDoc;
187
188         pdf_document->page = 1;
189
190         if (pdf_document->out)
191                 pdf_document->out->startDoc(pdf_document->doc->getXRef());
192
193         pdf_document->page_valid = FALSE;
194
195         g_object_notify (G_OBJECT (pdf_document), "title");
196
197         return TRUE;
198 }
199
200 static gboolean
201 pdf_document_save (EvDocument  *document,
202                    const char  *uri,
203                    GError     **error)
204 {
205         PdfDocument *pdf_document = PDF_DOCUMENT (document);
206         char *filename;
207         gboolean retval = FALSE;
208
209         filename = g_filename_from_uri (uri, NULL, error);
210         if (filename != NULL) {
211                 GString *fname = new GString (filename);
212
213                 retval = pdf_document->doc->saveAs (fname);
214         }
215
216         return retval;
217 }
218
219 static int
220 pdf_document_get_n_pages (EvDocument  *document)
221 {
222         PdfDocument *pdf_document = PDF_DOCUMENT (document);
223
224         if (pdf_document->doc)
225                 return pdf_document->doc->getNumPages();
226         else
227                 return 1;
228 }
229
230 static void
231 pdf_document_set_page (EvDocument  *document,
232                        int          page)
233 {
234         PdfDocument *pdf_document = PDF_DOCUMENT (document);
235
236         page = CLAMP (page, 1, ev_document_get_n_pages (document));
237
238         if (page != pdf_document->page) {
239                 pdf_document->page = page;
240                 pdf_document->page_valid = FALSE;
241         }
242
243         ev_document_changed (document);
244 }
245
246 static int
247 pdf_document_get_page (EvDocument  *document)
248 {
249         PdfDocument *pdf_document = PDF_DOCUMENT (document);
250
251         return pdf_document->page;
252 }
253
254 static void
255 redraw_callback (void *data)
256 {
257         /* Need to hook up through a EvDocument callback? */
258 }
259
260 static void
261 pdf_document_set_target (EvDocument  *document,
262                          GdkDrawable *target)
263 {
264         PdfDocument *pdf_document = PDF_DOCUMENT (document);
265
266         if (pdf_document->target != target) {
267                 if (pdf_document->target)
268                         g_object_unref (pdf_document->target);
269
270                 pdf_document->target = target;
271
272                 if (pdf_document->target)
273                         g_object_ref (pdf_document->target);
274
275                 if (pdf_document->out) {
276                         delete pdf_document->out;
277                         pdf_document->out = NULL;
278                 }
279
280                 if (pdf_document->target) {
281                         pdf_document->out = new GDKSplashOutputDev (gdk_drawable_get_screen (pdf_document->target),
282                                                          redraw_callback, (void*) document);
283
284                         if (pdf_document->doc)
285                                 pdf_document->out->startDoc(pdf_document->doc->getXRef());
286
287                 }
288
289                 pdf_document->page_valid = FALSE;
290         }
291 }
292
293 static void
294 pdf_document_set_scale (EvDocument  *document,
295                         double       scale)
296 {
297         PdfDocument *pdf_document = PDF_DOCUMENT (document);
298
299         if (pdf_document->scale != scale) {
300                 pdf_document->scale = scale;
301                 pdf_document->page_valid = FALSE;
302         }
303 }
304
305 static void
306 pdf_document_set_page_offset (EvDocument  *document,
307                               int          x,
308                               int          y)
309 {
310         PdfDocument *pdf_document = PDF_DOCUMENT (document);
311
312         pdf_document->page_x_offset = x;
313         pdf_document->page_y_offset = y;
314 }
315
316 static void
317 pdf_document_get_page_size (EvDocument   *document,
318                             int          *width,
319                             int          *height)
320 {
321         PdfDocument *pdf_document = PDF_DOCUMENT (document);
322
323         if (document_validate_page (pdf_document)) {
324                 if (width)
325                         *width = pdf_document->out->getBitmapWidth();
326                 if (height)
327                         *height = pdf_document->out->getBitmapHeight();
328         } else {
329                 if (width)
330                         *width = 1;
331                 if (height)
332                         *height = 1;
333         }
334 }
335
336 static void
337 pdf_document_render (EvDocument  *document,
338                      int          clip_x,
339                      int          clip_y,
340                      int          clip_width,
341                      int          clip_height)
342 {
343         PdfDocument *pdf_document = PDF_DOCUMENT (document);
344         GdkRectangle page;
345         GdkRectangle draw;
346
347         if (!document_validate_page (pdf_document) || !pdf_document->target)
348                 return;
349
350         page.x = pdf_document->page_x_offset;
351         page.y = pdf_document->page_y_offset;
352         page.width = pdf_document->out->getBitmapWidth();
353         page.height = pdf_document->out->getBitmapHeight();
354
355         draw.x = clip_x;
356         draw.y = clip_y;
357         draw.width = clip_width;
358         draw.height = clip_height;
359
360         if (gdk_rectangle_intersect (&page, &draw, &draw))
361                 pdf_document->out->redraw (draw.x - page.x, draw.y - page.y,
362                                            pdf_document->target,
363                                            draw.x, draw.y,
364                                            draw.width, draw.height);
365 }
366
367 static void
368 pdf_document_search_emit_found (PdfDocumentSearch *search)
369 {
370         PdfDocument *pdf_document = search->document;
371         int n_pages;
372         int pages_done;
373         GArray *tmp_results;
374         int i;
375
376         n_pages = ev_document_get_n_pages (EV_DOCUMENT (search->document));
377         if (search->search_page > search->start_page) {
378                 pages_done = search->search_page - search->start_page;
379         } else if (search->search_page == search->start_page) {
380                 pages_done = n_pages;
381         } else {
382                 pages_done = n_pages - search->start_page + search->search_page;
383         }
384
385         tmp_results = g_array_new (FALSE, FALSE, sizeof (EvFindResult));
386         g_array_append_vals (tmp_results,
387                              search->current_page_results->data,
388                              search->current_page_results->len);
389
390         /* Now append a bogus element for each page that has a result in it,
391          * that is not the current page
392          */
393         i = 1;
394         while (i <= n_pages) {
395                 if (i != pdf_document->page &&
396                     search->other_page_flags[i]) {
397                         EvFindResult result;
398
399                         result.page_num = i;
400
401                         /* Use bogus coordinates, again we can't get coordinates
402                          * until this is the current page because TextOutputDev
403                          * isn't good enough
404                          */
405                         result.highlight_area.x = -1;
406                         result.highlight_area.y = -1;
407                         result.highlight_area.width = 1;
408                         result.highlight_area.height = 1;
409
410                         g_array_append_val (tmp_results, result);
411                 }
412
413                 ++i;
414         }
415
416         ev_document_find_found (EV_DOCUMENT_FIND (pdf_document),
417                                 (EvFindResult*) tmp_results->data,
418                                 tmp_results->len,
419                                 pages_done / (double) n_pages);
420
421         g_array_free (tmp_results, TRUE);
422 }
423
424 static void
425 pdf_document_search_page_changed (PdfDocumentSearch   *search)
426 {
427         PdfDocument *pdf_document = search->document;
428         int current_page;
429         EvFindResult result;
430         int xMin, yMin, xMax, yMax;
431
432         current_page = pdf_document->page;
433
434         if (!pdf_document->page_valid) {
435                 /* we can't do anything until displayPage() */
436                 search->current_page = -1;
437                 return;
438         }
439
440         if (search->current_page == current_page)
441                 return;
442
443         /* We need to create current_page_results for the new current page */
444         g_array_set_size (search->current_page_results, 0);
445
446         if (pdf_document->out->findText (search->ucs4, search->ucs4_len,
447                                          gTrue, gTrue, // startAtTop, stopAtBottom
448                                          gFalse, gFalse, // startAtLast, stopAtLast
449                                          &xMin, &yMin, &xMax, &yMax)) {
450                 result.page_num = pdf_document->page;
451
452                 result.highlight_area.x = xMin;
453                 result.highlight_area.y = yMin;
454                 result.highlight_area.width = xMax - xMin;
455                 result.highlight_area.height = yMax - yMin;
456
457                 g_array_append_val (search->current_page_results, result);
458                 /* Now find further results */
459
460                 while (pdf_document->out->findText (search->ucs4, search->ucs4_len,
461                                                     gFalse, gTrue,
462                                                     gTrue, gFalse,
463                                                     &xMin, &yMin, &xMax, &yMax)) {
464
465                         result.page_num = pdf_document->page;
466
467                         result.highlight_area.x = xMin;
468                         result.highlight_area.y = yMin;
469                         result.highlight_area.width = xMax - xMin;
470                         result.highlight_area.height = yMax - yMin;
471
472                         g_array_append_val (search->current_page_results, result);
473                 }
474         }
475
476         /* needed for the initial current page since we don't search
477          * it in the idle
478          */
479         search->other_page_flags[current_page] =
480                 search->current_page_results->len > 0;
481
482         pdf_document_search_emit_found (search);
483 }
484
485 static gboolean
486 pdf_document_search_idle_callback (void *data)
487 {
488         PdfDocumentSearch *search = (PdfDocumentSearch*) data;
489         PdfDocument *pdf_document = search->document;
490         int n_pages;
491         double xMin, yMin, xMax, yMax;
492
493         /* Note that PDF page count is 1 through n_pages INCLUSIVE
494          * like a real book. We are looking to add one result for each
495          * page with a match, because the coordinates are meaningless
496          * with TextOutputDev, so we just want to flag matching pages
497          * and then when the user switches to the current page, we
498          * will emit "found" again with the real results.
499          */
500         n_pages = ev_document_get_n_pages (EV_DOCUMENT (search->document));
501
502         if (search->search_page == search->start_page) {
503                 goto end_search;
504         }
505
506         if (search->output_dev == 0) {
507                 /* First time through here... */
508                 search->output_dev = new TextOutputDev (NULL, gTrue, gFalse, gFalse);
509                 if (!search->output_dev->isOk()) {
510                         goto end_search;
511                 }
512         }
513
514         pdf_document->doc->displayPage (search->output_dev,
515                                         search->search_page,
516                                         72, 72, 0, gTrue, gFalse);
517
518         if (search->output_dev->findText (search->ucs4,
519                                           search->ucs4_len,
520                                           gTrue, gTrue, // startAtTop, stopAtBottom
521                                           gFalse, gFalse, // startAtLast, stopAtLast
522                                           &xMin, &yMin, &xMax, &yMax)) {
523                 /* This page has results */
524                 search->other_page_flags[search->search_page] = TRUE;
525         }
526
527         search->search_page += 1;
528         if (search->search_page > n_pages) {
529                 /* wrap around */
530                 search->search_page = 1;
531         }
532
533         /* We do this even if nothing was found, to update the percent complete */
534         pdf_document_search_emit_found (search);
535
536         return TRUE;
537
538  end_search:
539         /* We're done. */
540         search->idle = 0; /* will return FALSE to remove */
541         return FALSE;
542 }
543
544 static void
545 pdf_document_find_begin (EvDocumentFind   *document,
546                          const char       *search_string,
547                          gboolean          case_sensitive)
548 {
549         PdfDocument *pdf_document = PDF_DOCUMENT (document);
550         PdfDocumentSearch *search;
551         int n_pages;
552         gunichar *ucs4;
553         glong ucs4_len;
554
555         /* FIXME handle case_sensitive (right now XPDF
556          * code is always case insensitive for ASCII
557          * and case sensitive for all other languaages)
558          */
559
560         g_assert (sizeof (gunichar) == sizeof (Unicode));
561         ucs4 = g_utf8_to_ucs4_fast (search_string, -1,
562                                     &ucs4_len);
563
564         if (pdf_document->search &&
565             pdf_document->search->ucs4_len == ucs4_len &&
566             memcmp (pdf_document->search->ucs4,
567                     ucs4,
568                     sizeof (gunichar) * ucs4_len) == 0) {
569                 /* Search is unchanged */
570                 g_free (ucs4);
571                 return;
572         }
573
574         if (pdf_document->search) {
575                 pdf_document_search_free (pdf_document->search);
576                 pdf_document->search = NULL;
577         }
578
579         search = g_new0 (PdfDocumentSearch, 1);
580
581         search->ucs4 = ucs4;
582         search->ucs4_len = ucs4_len;
583
584         search->current_page_results = g_array_new (FALSE,
585                                                     FALSE,
586                                                     sizeof (EvFindResult));
587         n_pages = ev_document_get_n_pages (EV_DOCUMENT (document));
588
589         /* This is an array of bool; with the first value ignored
590          * so we can index by the based-at-1 page numbers
591          */
592         search->other_page_flags = g_new0 (guchar, n_pages + 1);
593
594         search->document = pdf_document;
595
596         /* We add at low priority so the progress bar repaints */
597         search->idle = g_idle_add_full (G_PRIORITY_LOW,
598                                         pdf_document_search_idle_callback,
599                                         search,
600                                         NULL);
601
602         search->output_dev = 0;
603
604         search->start_page = pdf_document->page;
605         search->search_page = search->start_page + 1;
606         if (search->search_page > n_pages)
607                 search->search_page = 1;
608
609         search->current_page = -1;
610
611         pdf_document->search = search;
612
613         /* Update for the current page right away */
614         pdf_document_search_page_changed (search);
615 }
616
617 static void
618 pdf_document_find_cancel (EvDocumentFind   *document)
619 {
620         PdfDocument *pdf_document = PDF_DOCUMENT (document);
621
622         if (pdf_document->search) {
623                 pdf_document_search_free (pdf_document->search);
624                 pdf_document->search = NULL;
625         }
626 }
627
628 static void
629 pdf_document_search_free (PdfDocumentSearch   *search)
630 {
631         if (search->idle != 0)
632                 g_source_remove (search->idle);
633
634         if (search->output_dev)
635                 delete search->output_dev;
636
637         g_array_free (search->current_page_results, TRUE);
638         g_free (search->other_page_flags);
639
640         g_free (search->ucs4);
641         g_free (search);
642 }
643
644 static void
645 pdf_document_ps_export_begin (EvPSExporter *exporter, const char *filename)
646 {
647         PdfDocument *document = PDF_DOCUMENT (exporter);
648
649         if (document->ps_out)
650                 delete document->ps_out;
651
652         document->ps_out = new PSOutputDev ((char *)filename, document->doc->getXRef(),
653                                             document->doc->getCatalog(), 1,
654                                             ev_document_get_n_pages (EV_DOCUMENT (document)),
655                                             psModePS);
656 }
657
658 static void
659 pdf_document_ps_export_do_page (EvPSExporter *exporter, int page)
660 {
661         PdfDocument *document = PDF_DOCUMENT (exporter);
662
663         document->doc->displayPage (document->ps_out, page,
664                                     72.0, 72.0, 0, gTrue, gFalse);
665 }
666
667 static void
668 pdf_document_ps_export_end (EvPSExporter *exporter)
669 {
670         PdfDocument *document = PDF_DOCUMENT (exporter);
671
672         delete document->ps_out;
673         document->ps_out = NULL;
674 }
675
676
677 /* EvDocumentBookmarks Implementation */
678 typedef struct
679 {
680         /* goo GList, not glib */
681         GList *items;
682         int index;
683         int level;
684 } BookmarksIter;
685
686 static gchar *
687 unicode_to_char (OutlineItem *outline_item,
688                  UnicodeMap *uMap)
689 {
690         GString gstr;
691         gchar buf[8]; /* 8 is enough for mapping an unicode char to a string */
692         int i, n;
693
694         for (i = 0; i < outline_item->getTitleLength(); ++i) {
695                 n = uMap->mapUnicode(outline_item->getTitle()[i], buf, sizeof(buf));
696                 gstr.append(buf, n);
697         }
698
699         return g_strdup (gstr.getCString ());
700 }
701
702
703 static gboolean
704 pdf_document_bookmarks_has_document_bookmarks (EvDocumentBookmarks *document_bookmarks)
705 {
706         PdfDocument *pdf_document = PDF_DOCUMENT (document_bookmarks);
707         Outline *outline;
708
709         g_return_val_if_fail (PDF_IS_DOCUMENT (document_bookmarks), FALSE);
710
711         outline = pdf_document->doc->getOutline();
712         if (outline->getItems() != NULL &&
713             outline->getItems()->getLength() > 0)
714                 return TRUE;
715
716         return FALSE;
717 }
718
719 static EvDocumentBookmarksIter *
720 pdf_document_bookmarks_begin_read (EvDocumentBookmarks *document_bookmarks)
721 {
722         PdfDocument *pdf_document = PDF_DOCUMENT (document_bookmarks);
723         Outline *outline;
724         BookmarksIter *iter;
725         GList *items;
726
727         g_return_val_if_fail (PDF_IS_DOCUMENT (document_bookmarks), NULL);
728
729         outline = pdf_document->doc->getOutline();
730         items = outline->getItems();
731         if (! items)
732                 return NULL;
733
734         iter = g_new0 (BookmarksIter, 1);
735         iter->items = items;
736         iter->index = 0;
737         iter->level = 0;
738
739         return (EvDocumentBookmarksIter *) iter;
740 }
741
742 static gboolean
743 pdf_document_bookmarks_get_values (EvDocumentBookmarks      *document_bookmarks,
744                                    EvDocumentBookmarksIter  *bookmarks_iter,
745                                    char                    **title,
746                                    EvDocumentBookmarksType  *type,
747                                    gint                     *page)
748 {
749         PdfDocument *pdf_document = PDF_DOCUMENT (document_bookmarks);
750         BookmarksIter *iter = (BookmarksIter *)bookmarks_iter;
751         OutlineItem *anItem;
752         LinkAction *link_action;
753         LinkDest *link_dest = NULL;
754         LinkURI *link_uri = NULL;
755         LinkGoTo *link_goto = NULL;
756         GString *named_dest;
757         Unicode *link_title;
758         Ref page_ref;
759         gint page_num = -1;
760
761         g_return_val_if_fail (PDF_IS_DOCUMENT (document_bookmarks), FALSE);
762         g_return_val_if_fail (iter != NULL, FALSE);
763         g_return_val_if_fail (title != NULL, FALSE);
764         g_return_val_if_fail (type != NULL, FALSE);
765         g_return_val_if_fail (page != NULL, FALSE);
766
767         anItem = (OutlineItem *)iter->items->get(iter->index);
768         link_action = anItem->getAction ();
769         link_title = anItem->getTitle ();
770
771         if (link_action) {
772                 switch (link_action->getKind ()) {
773
774                 case actionGoTo:
775                         link_goto = dynamic_cast <LinkGoTo *> (link_action);
776                         link_dest = link_goto->getDest ();
777                         named_dest = link_goto->getNamedDest ();
778
779                         /* Wow!  This seems excessively slow on large
780                          * documents. I need to investigate more... -jrb */
781                         if (link_dest != NULL) {
782                                 link_dest = link_dest->copy ();
783                         } else if (named_dest != NULL) {
784                                 named_dest = named_dest->copy ();
785                                 link_dest = pdf_document->doc->findDest (named_dest);
786                                 delete named_dest;
787                         }
788                         if (link_dest != NULL) {
789                                 if (link_dest->isPageRef ()) {
790                                         page_ref = link_dest->getPageRef ();
791                                         page_num = pdf_document->doc->findPage (page_ref.num, page_ref.gen);
792                                 } else {
793                                         page_num = link_dest->getPageNum ();
794                                 }
795
796                                 delete link_dest;
797                         }
798
799                         break;
800                 case actionURI:
801                         link_uri = dynamic_cast <LinkURI *> (link_action);
802                         break;
803
804                 case actionNamed:
805                         /*Skip, for now */
806                 default:
807                         g_warning ("Unknown link action type: %d", link_action->getKind ());
808                 }
809
810                 *title = g_strdup (unicode_to_char (anItem, pdf_document->umap));
811         } else if (link_title) {
812                 *title = g_strdup (unicode_to_char (anItem, pdf_document->umap));
813         }
814
815         *type = EV_DOCUMENT_BOOKMARKS_TYPE_LINK;
816         *page = page_num;
817
818         return TRUE;
819 }
820
821 static EvDocumentBookmarksIter *
822 pdf_document_bookmarks_get_child (EvDocumentBookmarks     *document_bookmarks,
823                                   EvDocumentBookmarksIter *bookmarks_iter)
824 {
825         BookmarksIter *iter = (BookmarksIter *)bookmarks_iter;
826         BookmarksIter *child_iter;
827         OutlineItem *anItem;
828
829         g_return_val_if_fail (PDF_IS_DOCUMENT (document_bookmarks), FALSE);
830
831         anItem = (OutlineItem *)iter->items->get(iter->index);
832         anItem->open ();
833         if (! (anItem->hasKids() && anItem->getKids()) )
834                 return NULL;
835
836         child_iter = g_new0 (BookmarksIter, 1);
837         child_iter->index = 0;
838         child_iter->level = iter->level + 1;
839         child_iter->items = anItem->getKids ();
840         g_assert (child_iter->items);
841
842         return (EvDocumentBookmarksIter *) child_iter;
843 }
844
845 static gboolean
846 pdf_document_bookmarks_next (EvDocumentBookmarks     *document_bookmarks,
847                              EvDocumentBookmarksIter *bookmarks_iter)
848 {
849         BookmarksIter *iter = (BookmarksIter *) bookmarks_iter;
850
851         g_return_val_if_fail (PDF_IS_DOCUMENT (document_bookmarks), FALSE);
852
853         iter->index++;
854         if (iter->index >= iter->items->getLength())
855                 return FALSE;
856
857         return TRUE;
858 }
859
860 static void
861 pdf_document_bookmarks_free_iter (EvDocumentBookmarks     *document_bookmarks,
862                                   EvDocumentBookmarksIter *iter)
863 {
864         g_return_if_fail (PDF_IS_DOCUMENT (document_bookmarks));
865         g_return_if_fail (iter != NULL);
866
867         /* FIXME: Should I close all the nodes?? Free them? */
868         g_free (iter);
869 }
870
871 static void
872 pdf_document_finalize (GObject *object)
873 {
874         PdfDocument *pdf_document = PDF_DOCUMENT (object);
875
876         if (pdf_document->umap) {
877                 pdf_document->umap->decRefCnt ();
878                 pdf_document->umap = NULL;
879         }
880
881         if (pdf_document->search)
882                 pdf_document_search_free (pdf_document->search);
883
884         if (pdf_document->target)
885                 g_object_unref (pdf_document->target);
886
887         if (pdf_document->out)
888                 delete pdf_document->out;
889         if (pdf_document->ps_out)
890                 delete pdf_document->ps_out;
891         if (pdf_document->doc)
892                 delete pdf_document->doc;
893
894 }
895
896 static void
897 pdf_document_set_property (GObject *object,
898                            guint prop_id,
899                            const GValue *value,
900                            GParamSpec *pspec)
901 {
902         switch (prop_id)
903
904         {
905                 case PROP_TITLE:
906                         /* read only */
907                         break;
908         }
909 }
910
911 static gboolean
912 has_unicode_marker (GString *string)
913 {
914         return ((string->getChar (0) & 0xff) == 0xfe &&
915                 (string->getChar (1) & 0xff) == 0xff);
916 }
917
918 static gchar *
919 pdf_info_dict_get_string (Dict *info_dict, const gchar *key) {
920         Object obj;
921         GString *value;
922         gchar *result;
923
924         g_return_val_if_fail (info_dict != NULL, NULL);
925         g_return_val_if_fail (key != NULL, NULL);
926
927         if (!info_dict->lookup ((gchar *)key, &obj)->isString ()) {
928                 obj.free ();
929                 return g_strdup (_("Unknown"));
930         }
931
932         value = obj.getString ();
933
934         if (has_unicode_marker (value)) {
935                 result = g_convert (value->getCString () + 2,
936                                     value->getLength () - 2,
937                                     "UTF-8", "UTF-16BE", NULL, NULL, NULL);
938         } else {
939                 result = g_strndup (value->getCString (), value->getLength ());
940         }
941
942         obj.free ();
943
944         return result;
945 }
946
947 static char *
948 pdf_document_get_title (PdfDocument *pdf_document)
949 {
950         char *title = NULL;
951         Object info;
952
953         pdf_document->doc->getDocInfo (&info);
954
955         if (info.isDict ()) {
956                 title = pdf_info_dict_get_string (info.getDict(), "Title");
957         }
958
959         return title;
960 }
961
962 static void
963 pdf_document_get_property (GObject *object,
964                            guint prop_id,
965                            GValue *value,
966                            GParamSpec *pspec)
967 {
968         PdfDocument *pdf_document = PDF_DOCUMENT (object);
969         char *title;
970
971         switch (prop_id)
972         {
973                 case PROP_TITLE:
974                         title = pdf_document_get_title (pdf_document);
975                         g_value_set_string (value, title);
976                         g_free (title);
977                         break;
978         }
979 }
980
981 static void
982 pdf_document_class_init (PdfDocumentClass *klass)
983 {
984         GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
985
986         gobject_class->finalize = pdf_document_finalize;
987         gobject_class->get_property = pdf_document_get_property;
988         gobject_class->set_property = pdf_document_set_property;
989
990         g_object_class_override_property (gobject_class, PROP_TITLE, "title");
991 }
992
993 static void
994 pdf_document_document_iface_init (EvDocumentIface *iface)
995 {
996         iface->load = pdf_document_load;
997         iface->save = pdf_document_save;
998         iface->get_n_pages = pdf_document_get_n_pages;
999         iface->set_page = pdf_document_set_page;
1000         iface->get_page = pdf_document_get_page;
1001         iface->set_scale = pdf_document_set_scale;
1002         iface->set_target = pdf_document_set_target;
1003         iface->set_page_offset = pdf_document_set_page_offset;
1004         iface->get_page_size = pdf_document_get_page_size;
1005         iface->render = pdf_document_render;
1006 }
1007
1008 static void
1009 pdf_document_ps_exporter_iface_init (EvPSExporterIface *iface)
1010 {
1011         iface->begin = pdf_document_ps_export_begin;
1012         iface->do_page = pdf_document_ps_export_do_page;
1013         iface->end = pdf_document_ps_export_end;
1014 }
1015
1016
1017 static void
1018 pdf_document_find_iface_init (EvDocumentFindIface *iface)
1019 {
1020         iface->begin = pdf_document_find_begin;
1021         iface->cancel = pdf_document_find_cancel;
1022 }
1023
1024 static void
1025 pdf_document_document_bookmarks_iface_init (EvDocumentBookmarksIface *iface)
1026 {
1027         iface->has_document_bookmarks = pdf_document_bookmarks_has_document_bookmarks;
1028         iface->begin_read = pdf_document_bookmarks_begin_read;
1029         iface->get_values = pdf_document_bookmarks_get_values;
1030         iface->get_child = pdf_document_bookmarks_get_child;
1031         iface->next = pdf_document_bookmarks_next;
1032         iface->free_iter = pdf_document_bookmarks_free_iter;
1033 }
1034
1035 /* Thumbnails */
1036 static GdkPixbuf *
1037 pdf_document_thumbnails_get_page_pixbuf (PdfDocument *pdf_document,
1038                                          gdouble      scale_factor,
1039                                          gint         page_num,
1040                                          gint         width,
1041                                          gint         height)
1042 {
1043         GdkPixmap *pixmap;
1044         GDKSplashOutputDev *output;
1045         GdkPixbuf *pixbuf;
1046         GdkPixbuf *shadow;
1047
1048         pixmap = gdk_pixmap_new (pdf_document->target,
1049                                  width, height, -1);
1050
1051         output = new GDKSplashOutputDev (gdk_drawable_get_screen (pdf_document->target),
1052                                          NULL, NULL);
1053         output->startDoc (pdf_document->doc->getXRef());
1054         pdf_document->doc->displayPage (output,
1055                                         page_num + 1,
1056                                         72*scale_factor,
1057                                         72*scale_factor,
1058                                         0, gTrue, gFalse);
1059         output->redraw (0, 0,
1060                         pixmap,
1061                         0, 0,
1062                         width, height);
1063         pixbuf = gdk_pixbuf_get_from_drawable (NULL,
1064                                                pixmap,
1065                                                NULL,
1066                                                0, 0,
1067                                                0, 0,
1068                                                width, height);
1069         gdk_drawable_unref (pixmap);
1070         delete output;
1071
1072         shadow = ev_document_misc_get_thumbnail_frame (-1, -1, pixbuf);
1073         g_object_unref (pixbuf);
1074
1075         return shadow;
1076 }
1077
1078 static GdkPixbuf *
1079 pdf_document_thumbnails_get_thumbnail (EvDocumentThumbnails *document_thumbnails,
1080                                        gint                  page,
1081                                        gint                  width)
1082 {
1083         PdfDocument *pdf_document = PDF_DOCUMENT (document_thumbnails);
1084         GdkPixbuf *thumbnail;
1085         Page *the_page;
1086         Object the_thumb;
1087         Thumb *thumb = NULL;
1088         gboolean have_ethumbs = FALSE;
1089         gdouble page_ratio;
1090         gint dest_height;
1091
1092         /* getPage seems to want page + 1 for some reason; */
1093         the_page = pdf_document->doc->getCatalog ()->getPage (page + 1);
1094         the_page->getThumb(&the_thumb);
1095
1096         page_ratio = the_page->getHeight () / the_page->getWidth ();
1097         dest_height = (gint) (width * page_ratio);
1098
1099
1100         if (!(the_thumb.isNull () || the_thumb.isNone())) {
1101                 /* Build the thumbnail object */
1102                 thumb = new Thumb(pdf_document->doc->getXRef (),
1103                                   &the_thumb);
1104
1105                 have_ethumbs = thumb->ok();
1106         }
1107
1108         if (have_ethumbs) {
1109                 guchar *data;
1110                 GdkPixbuf *tmp_pixbuf;
1111
1112                 data = thumb->getPixbufData();
1113                 tmp_pixbuf = gdk_pixbuf_new_from_data (data,
1114                                                        GDK_COLORSPACE_RGB,
1115                                                        FALSE,
1116                                                        8,
1117                                                        thumb->getWidth (),
1118                                                        thumb->getHeight (),
1119                                                        thumb->getWidth () * 3,
1120                                                        NULL, NULL);
1121                 /* FIXME: do we want to check that the thumb's size isn't ridiculous?? */
1122                 thumbnail = ev_document_misc_get_thumbnail_frame (-1, -1, tmp_pixbuf);
1123                 g_object_unref (tmp_pixbuf);
1124         } else {
1125                 gdouble scale_factor;
1126
1127                 scale_factor = (gdouble)width / the_page->getWidth ();
1128
1129                 thumbnail = pdf_document_thumbnails_get_page_pixbuf (pdf_document,
1130                                                                      scale_factor,
1131                                                                      page,
1132                                                                      width,
1133                                                                      dest_height);
1134         }
1135
1136         return thumbnail;
1137 }
1138 static void
1139 pdf_document_document_thumbnails_iface_init (EvDocumentThumbnailsIface *iface)
1140 {
1141         iface->get_thumbnail = pdf_document_thumbnails_get_thumbnail;
1142 }
1143
1144
1145 static void
1146 pdf_document_init (PdfDocument *pdf_document)
1147 {
1148         pdf_document->page = 1;
1149         pdf_document->page_x_offset = 0;
1150         pdf_document->page_y_offset = 0;
1151         pdf_document->scale = 1.;
1152
1153         pdf_document->page_valid = FALSE;
1154 }
1155