]> www.fi.muni.cz Git - evince.git/blob - pdf/xpdf/pdf-document.cc
64d592ca2d0c66f8f9b8873cec464962be3b9278
[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 "gpdf-g-switch.h"
21 #include "pdf-document.h"
22 #include "ev-ps-exporter.h"
23 #include "ev-document-find.h"
24 #include "gpdf-g-switch.h"
25
26 #include "GlobalParams.h"
27 #include "GDKSplashOutputDev.h"
28 #include "PDFDoc.h"
29 #include "PSOutputDev.h"
30
31 typedef struct
32 {
33         PdfDocument *document;
34         gunichar *ucs4;
35         glong ucs4_len;
36         guint idle;
37         /* full results are only possible for the rendered current page */
38         int current_page;
39         GArray *current_page_results;
40         guchar *other_page_flags; /* length n_pages + 1, first element ignored */
41         int start_page;   /* skip this one as we iterate, since we did it first */
42         int search_page;  /* the page we're searching now */
43         TextOutputDev *output_dev;
44 } PdfDocumentSearch;
45
46 typedef struct _PdfDocumentClass PdfDocumentClass;
47
48 #define PDF_DOCUMENT_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST ((klass), PDF_TYPE_DOCUMENT, PdfDocumentClass))
49 #define PDF_IS_DOCUMENT_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE ((klass), PDF_TYPE_DOCUMENT))
50 #define PDF_DOCUMENT_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS ((obj), PDF_TYPE_DOCUMENT, PdfDocumentClass))
51
52 struct _PdfDocumentClass
53 {
54         GObjectClass parent_class;
55 };
56
57 struct _PdfDocument
58 {
59         GObject parent_instance;
60
61         int page;
62         int page_x_offset;
63         int page_y_offset;
64         double scale;
65         GdkDrawable *target;
66
67         GDKSplashOutputDev *out;
68         PSOutputDev *ps_out;
69         PDFDoc *doc;
70
71         gboolean page_valid;
72
73         PdfDocumentSearch *search;
74 };
75
76 static void pdf_document_document_iface_init    (EvDocumentIface     *iface);
77 static void pdf_document_ps_exporter_iface_init (EvPSExporterIface   *iface);
78 static void pdf_document_find_iface_init        (EvDocumentFindIface *iface);
79 static void pdf_document_search_free            (PdfDocumentSearch   *search);
80 static void pdf_document_search_page_changed    (PdfDocumentSearch   *search);
81
82 G_DEFINE_TYPE_WITH_CODE (PdfDocument, pdf_document, G_TYPE_OBJECT,
83                          {
84                                  G_IMPLEMENT_INTERFACE (EV_TYPE_DOCUMENT,
85                                                         pdf_document_document_iface_init);
86                                  G_IMPLEMENT_INTERFACE (EV_TYPE_PS_EXPORTER,
87                                                         pdf_document_ps_exporter_iface_init);
88                                  G_IMPLEMENT_INTERFACE (EV_TYPE_DOCUMENT_FIND,
89                                                         pdf_document_find_iface_init);
90                          });
91
92 static gboolean
93 document_validate_page (PdfDocument *pdf_document)
94 {
95         if (!pdf_document->page_valid) {
96                 pdf_document->doc->displayPage (pdf_document->out, pdf_document->page,
97                                                 72 * pdf_document->scale,
98                                                 72 * pdf_document->scale,
99                                                 0, gTrue, gTrue);
100                 
101                 pdf_document->page_valid = TRUE;
102
103                 /* Update the search results available to the app since
104                  * we only provide full results on the current page
105                  */
106                 if (pdf_document->search)
107                         pdf_document_search_page_changed (pdf_document->search);
108         }
109 }
110
111 static gboolean
112 pdf_document_load (EvDocument  *document,
113                    const char  *uri,
114                    GError     **error)
115 {
116         PdfDocument *pdf_document = PDF_DOCUMENT (document);
117         PDFDoc *newDoc;
118         int err;
119         char *filename;
120         GString *filename_g;
121         
122         if (!globalParams) {
123                 globalParams = new GlobalParams("/etc/xpdfrc");
124                 globalParams->setupBaseFontsFc(NULL);
125         }
126
127         filename = g_filename_from_uri (uri, NULL, error);
128         if (!filename)
129                 return FALSE;
130
131         filename_g = new GString (filename);
132         g_free (filename);
133
134         // open the PDF file, assumes ownership of filename_g
135         newDoc = new PDFDoc(filename_g, 0, 0);
136
137         if (!newDoc->isOk()) {
138                 err = newDoc->getErrorCode();
139                 delete newDoc;
140
141                 /* FIXME: Add a real error enum to EvDocument */
142                 g_set_error (error, G_FILE_ERROR,
143                              G_FILE_ERROR_FAILED,
144                              "Failed to load document (error %d) '%s'\n",
145                              err,
146                              uri);
147                 
148                 return FALSE;
149         }
150
151         if (pdf_document->doc)
152                 delete pdf_document->doc;
153         pdf_document->doc = newDoc;
154
155         pdf_document->page = 1;
156
157         if (pdf_document->out)
158                 pdf_document->out->startDoc(pdf_document->doc->getXRef());
159
160         pdf_document->page_valid = FALSE;
161         
162         return TRUE;
163 }
164
165 static int
166 pdf_document_get_n_pages (EvDocument  *document)
167 {
168         PdfDocument *pdf_document = PDF_DOCUMENT (document);
169
170         if (pdf_document->doc)
171                 return pdf_document->doc->getNumPages();
172         else
173                 return 1;
174 }
175
176 static void
177 pdf_document_set_page (EvDocument  *document,
178                        int          page)
179 {
180         PdfDocument *pdf_document = PDF_DOCUMENT (document);
181
182         page = CLAMP (page, 1, ev_document_get_n_pages (document));
183
184         if (page != pdf_document->page) {
185                 pdf_document->page = page;
186                 pdf_document->page_valid = FALSE;
187         }
188
189 }
190
191 static int
192 pdf_document_get_page (EvDocument  *document)
193 {
194         PdfDocument *pdf_document = PDF_DOCUMENT (document);
195
196         return pdf_document->page;
197 }
198
199 static void
200 redraw_callback (void *data)
201 {
202         /* Need to hook up through a EvDocument callback? */
203 }
204
205 static void
206 pdf_document_set_target (EvDocument  *document,
207                          GdkDrawable *target)
208 {
209         PdfDocument *pdf_document = PDF_DOCUMENT (document);
210         
211         if (pdf_document->target != target) {
212                 if (pdf_document->target)
213                         g_object_unref (pdf_document->target);
214                 
215                 pdf_document->target = target;
216
217                 if (pdf_document->target)
218                         g_object_ref (pdf_document->target);
219
220                 if (pdf_document->out) {
221                         delete pdf_document->out;
222                         pdf_document->out = NULL;
223                 }
224
225                 if (pdf_document->target) {
226                         pdf_document->out = new GDKSplashOutputDev (gdk_drawable_get_screen (pdf_document->target),
227                                                          redraw_callback, (void*) document);
228
229                         if (pdf_document->doc)
230                                 pdf_document->out->startDoc(pdf_document->doc->getXRef());
231
232                 }
233
234                 pdf_document->page_valid = FALSE;
235         }
236 }
237
238 static void
239 pdf_document_set_scale (EvDocument  *document,
240                         double       scale)
241 {
242         PdfDocument *pdf_document = PDF_DOCUMENT (document);
243         
244         if (pdf_document->scale != scale) {
245                 pdf_document->scale = scale;
246                 pdf_document->page_valid = FALSE;
247         }
248 }
249
250 static void
251 pdf_document_set_page_offset (EvDocument  *document,
252                               int          x,
253                               int          y)
254 {
255         PdfDocument *pdf_document = PDF_DOCUMENT (document);
256         
257         pdf_document->page_x_offset = x;
258         pdf_document->page_y_offset = y;
259 }
260
261 static void
262 pdf_document_get_page_size (EvDocument   *document,
263                             int          *width,
264                             int          *height)
265 {
266         PdfDocument *pdf_document = PDF_DOCUMENT (document);
267
268         if (document_validate_page (pdf_document)) {
269                 if (width)
270                         *width = pdf_document->out->getBitmapWidth();
271                 if (height)
272                         *height = pdf_document->out->getBitmapHeight();
273         } else {
274                 if (width)
275                         *width = 1;
276                 if (height)
277                         *height = 1;
278         }
279 }
280
281 static void
282 pdf_document_render (EvDocument  *document,
283                      int          clip_x,
284                      int          clip_y,
285                      int          clip_width,
286                      int          clip_height)
287 {
288         PdfDocument *pdf_document = PDF_DOCUMENT (document);
289         GdkRectangle page;
290         GdkRectangle draw;
291
292         if (!document_validate_page (pdf_document) || !pdf_document->target)
293                 return;
294         
295         page.x = pdf_document->page_x_offset;
296         page.y = pdf_document->page_y_offset;
297         page.width = pdf_document->out->getBitmapWidth();
298         page.height = pdf_document->out->getBitmapHeight();
299
300         draw.x = clip_x;
301         draw.y = clip_y;
302         draw.width = clip_width;
303         draw.height = clip_height;
304         
305         if (gdk_rectangle_intersect (&page, &draw, &draw))
306                 pdf_document->out->redraw (draw.x - page.x, draw.y - page.y,
307                                            pdf_document->target,
308                                            draw.x, draw.y,
309                                            draw.width, draw.height);
310 }
311
312 static void
313 pdf_document_search_emit_found (PdfDocumentSearch *search)
314 {
315         PdfDocument *pdf_document = search->document;
316         int n_pages;
317         double pages_done;
318         GArray *tmp_results;
319         int i;
320
321         n_pages = ev_document_get_n_pages (EV_DOCUMENT (search->document));
322         if (search->search_page > search->start_page) {
323                 pages_done = search->search_page - search->start_page;
324         } else {
325                 pages_done = n_pages - search->start_page + search->search_page;
326         }
327
328         tmp_results = g_array_new (FALSE, FALSE, sizeof (EvFindResult));
329         g_array_append_vals (tmp_results,
330                              search->current_page_results->data,
331                              search->current_page_results->len);
332
333         /* Now append a bogus element for each page that has a result in it,
334          * that is not the current page
335          */
336         i = 1;
337         while (i <= n_pages) {
338                 if (i != pdf_document->page &&
339                     search->other_page_flags[i]) {
340                         EvFindResult result;
341                         
342                         result.page_num = i;
343                         
344                         /* Use bogus coordinates, again we can't get coordinates
345                          * until this is the current page because TextOutputDev
346                          * isn't good enough
347                          */
348                         result.highlight_area.x = -1;
349                         result.highlight_area.y = -1;
350                         result.highlight_area.width = 1;
351                         result.highlight_area.height = 1;
352                         
353                         g_array_append_val (tmp_results, result);
354                 }
355
356                 ++i;
357         }
358         
359         ev_document_find_found (EV_DOCUMENT_FIND (pdf_document),
360                                 (EvFindResult*) tmp_results->data,
361                                 tmp_results->len,
362                                 pages_done / (double) n_pages);
363         
364         g_array_free (tmp_results, TRUE);
365 }
366
367 static void
368 pdf_document_search_page_changed (PdfDocumentSearch   *search)
369 {
370         PdfDocument *pdf_document = search->document;
371         int current_page;
372         EvFindResult result;
373         int xMin, yMin, xMax, yMax;
374
375         current_page = pdf_document->page;
376
377         if (!pdf_document->page_valid) {
378                 /* we can't do anything until displayPage() */
379                 search->current_page = -1;
380                 return;
381         }
382         
383         if (search->current_page == current_page)
384                 return;
385         
386         /* We need to create current_page_results for the new current page */
387         g_array_set_size (search->current_page_results, 0);
388         
389         if (pdf_document->out->findText (search->ucs4, search->ucs4_len,
390                                          gTrue, gTrue, // startAtTop, stopAtBottom
391                                          gFalse, gFalse, // startAtLast, stopAtLast
392                                          &xMin, &yMin, &xMax, &yMax)) {
393                 result.page_num = pdf_document->page;
394
395                 result.highlight_area.x = xMin;
396                 result.highlight_area.y = yMin;
397                 result.highlight_area.width = xMax - xMin;
398                 result.highlight_area.height = yMax - yMin;
399
400                 g_array_append_val (search->current_page_results, result);
401         
402                 /* Now find further results */
403
404                 while (pdf_document->out->findText (search->ucs4, search->ucs4_len,
405                                                     gFalse, gTrue,
406                                                     gTrue, gFalse,
407                                                     &xMin, &yMin, &xMax, &yMax)) {
408                         
409                         result.page_num = pdf_document->page;
410                         
411                         result.highlight_area.x = xMin;
412                         result.highlight_area.y = yMin;
413                         result.highlight_area.width = xMax - xMin;
414                         result.highlight_area.height = yMax - yMin;
415                         
416                         g_array_append_val (search->current_page_results, result);
417                 }
418         }
419
420         /* needed for the initial current page since we don't search
421          * it in the idle
422          */
423         search->other_page_flags[current_page] =
424                 search->current_page_results->len > 0;
425         
426         pdf_document_search_emit_found (search);
427 }
428
429 static gboolean
430 pdf_document_search_idle_callback (void *data)
431 {
432         PdfDocumentSearch *search = (PdfDocumentSearch*) data;
433         PdfDocument *pdf_document = search->document;
434         int n_pages;
435         double xMin, yMin, xMax, yMax;
436         gboolean found;
437
438         /* Note that PDF page count is 1 through n_pages INCLUSIVE
439          * like a real book. We are looking to add one result for each
440          * page with a match, because the coordinates are meaningless
441          * with TextOutputDev, so we just want to flag matching pages
442          * and then when the user switches to the current page, we
443          * will emit "found" again with the real results.
444          */
445         n_pages = ev_document_get_n_pages (EV_DOCUMENT (search->document));
446
447         if (search->search_page == search->start_page) {
448                 goto end_search;
449         }
450
451         if (search->output_dev == 0) {
452                 /* First time through here... */
453                 search->output_dev = new TextOutputDev (NULL, gTrue, gFalse, gFalse);
454                 if (!search->output_dev->isOk()) {
455                         goto end_search;
456                 }
457         }
458                                                   
459         pdf_document->doc->displayPage (search->output_dev,
460                                         search->search_page,
461                                         72, 72, 0, gTrue, gFalse);
462
463         if (search->output_dev->findText (search->ucs4,
464                                           search->ucs4_len,
465                                           gTrue, gTrue, // startAtTop, stopAtBottom
466                                           gFalse, gFalse, // startAtLast, stopAtLast
467                                           &xMin, &yMin, &xMax, &yMax)) {
468                 /* This page has results */
469                 search->other_page_flags[search->search_page] = TRUE;
470                 
471                 pdf_document_search_emit_found (search);
472         }
473
474         search->search_page += 1;
475         if (search->search_page > n_pages) {
476                 /* wrap around */
477                 search->search_page = 1;
478         }
479         
480         return TRUE;
481
482  end_search:
483         /* We're done. */
484         search->idle = 0; /* will return FALSE to remove */
485         return FALSE;
486 }
487
488 static void
489 pdf_document_find_begin (EvDocumentFind   *document,
490                          const char       *search_string,
491                          gboolean          case_sensitive)
492 {
493         PdfDocument *pdf_document = PDF_DOCUMENT (document);
494         PdfDocumentSearch *search;
495         int n_pages;
496         gunichar *ucs4;
497         glong ucs4_len;
498
499         /* FIXME handle case_sensitive (right now XPDF
500          * code is always case insensitive for ASCII
501          * and case sensitive for all other languaages)
502          */
503         
504         g_assert (sizeof (gunichar) == sizeof (Unicode));
505         ucs4 = g_utf8_to_ucs4_fast (search_string, -1,
506                                     &ucs4_len);
507
508         if (pdf_document->search &&
509             pdf_document->search->ucs4_len == ucs4_len &&
510             memcmp (pdf_document->search->ucs4,
511                     ucs4,
512                     sizeof (gunichar) * ucs4_len) == 0) {
513                 /* Search is unchanged */
514                 g_free (ucs4);
515                 return;
516         }
517
518         if (pdf_document->search) {
519                 pdf_document_search_free (pdf_document->search);
520                 pdf_document->search = NULL;
521         }
522         
523         search = g_new0 (PdfDocumentSearch, 1);
524
525         search->ucs4 = ucs4;
526         search->ucs4_len = ucs4_len;
527         
528         search->current_page_results = g_array_new (FALSE,
529                                                     FALSE,
530                                                     sizeof (EvFindResult));
531         n_pages = ev_document_get_n_pages (EV_DOCUMENT (document)); 
532
533         /* This is an array of bool; with the first value ignored
534          * so we can index by the based-at-1 page numbers
535          */
536         search->other_page_flags = g_new0 (guchar, n_pages + 1);
537         
538         search->document = pdf_document;
539
540         search->idle = g_idle_add (pdf_document_search_idle_callback,
541                                    search);
542
543         search->output_dev = 0;
544
545         search->start_page = pdf_document->page;
546         search->search_page = search->start_page + 1;
547         if (search->search_page > n_pages)
548                 search->search_page = 1;
549
550         search->current_page = -1;
551
552         pdf_document->search = search;
553         
554         /* Update for the current page right away */
555         pdf_document_search_page_changed (search);
556 }
557
558 static void
559 pdf_document_find_cancel (EvDocumentFind   *document)
560 {
561         PdfDocument *pdf_document = PDF_DOCUMENT (document);
562
563         if (pdf_document->search) {
564                 pdf_document_search_free (pdf_document->search);
565                 pdf_document->search = NULL;
566         }
567 }
568
569 static void
570 pdf_document_search_free (PdfDocumentSearch   *search)
571 {
572         if (search->idle != 0)
573                 g_source_remove (search->idle);
574
575         if (search->output_dev)
576                 delete search->output_dev;
577         
578         g_array_free (search->current_page_results, TRUE);
579         g_free (search->other_page_flags);
580         
581         g_free (search->ucs4);
582         g_free (search);
583 }
584
585 static void
586 pdf_document_ps_export_begin (EvPSExporter *exporter, const char *filename)
587 {
588         PdfDocument *document = PDF_DOCUMENT (exporter);
589
590         if (document->ps_out)
591                 delete document->ps_out;
592
593         document->ps_out = new PSOutputDev ((char *)filename, document->doc->getXRef(),
594                                             document->doc->getCatalog(), 1,
595                                             ev_document_get_n_pages (EV_DOCUMENT (document)),
596                                             psModePS);  
597 }
598
599 static void
600 pdf_document_ps_export_do_page (EvPSExporter *exporter, int page)
601 {
602         PdfDocument *document = PDF_DOCUMENT (exporter);
603
604         document->doc->displayPage (document->ps_out, page,
605                                     72.0, 72.0, 0, gTrue, gFalse);
606 }
607
608 static void
609 pdf_document_ps_export_end (EvPSExporter *exporter)
610 {
611         PdfDocument *document = PDF_DOCUMENT (exporter);
612
613         delete document->ps_out;
614         document->ps_out = NULL;
615 }
616
617 static void
618 pdf_document_finalize (GObject *object)
619 {
620         PdfDocument *pdf_document = PDF_DOCUMENT (object);
621
622         if (pdf_document->search)
623                 pdf_document_search_free (pdf_document->search);
624         
625         if (pdf_document->target)
626                 g_object_unref (pdf_document->target);
627
628         if (pdf_document->out)
629                 delete pdf_document->out;
630         if (pdf_document->ps_out)
631                 delete pdf_document->ps_out;
632         if (pdf_document->doc)
633                 delete pdf_document->doc;
634
635 }
636
637 static void
638 pdf_document_class_init (PdfDocumentClass *klass)
639 {
640         GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
641   
642         gobject_class->finalize = pdf_document_finalize;
643 }
644
645 static void
646 pdf_document_document_iface_init (EvDocumentIface *iface)
647 {
648         iface->load = pdf_document_load;
649         iface->get_n_pages = pdf_document_get_n_pages;
650         iface->set_page = pdf_document_set_page;
651         iface->get_page = pdf_document_get_page;
652         iface->set_scale = pdf_document_set_scale;
653         iface->set_target = pdf_document_set_target;
654         iface->set_page_offset = pdf_document_set_page_offset;
655         iface->get_page_size = pdf_document_get_page_size;
656         iface->render = pdf_document_render;
657 }
658
659 static void
660 pdf_document_ps_exporter_iface_init (EvPSExporterIface *iface)
661 {
662         iface->begin = pdf_document_ps_export_begin;
663         iface->do_page = pdf_document_ps_export_do_page;
664         iface->end = pdf_document_ps_export_end;
665 }
666
667
668 static void
669 pdf_document_find_iface_init (EvDocumentFindIface *iface)
670 {
671         iface->begin = pdf_document_find_begin;
672         iface->cancel = pdf_document_find_cancel;
673 }
674
675 static void
676 pdf_document_init (PdfDocument *pdf_document)
677 {
678         pdf_document->page = 1;
679         pdf_document->page_x_offset = 0;
680         pdf_document->page_y_offset = 0;
681         pdf_document->scale = 1.;
682         
683         pdf_document->page_valid = FALSE;
684 }
685