]> code.delx.au - gnu-emacs/blob - lisp/doc-view.el
Merge from emacs--rel--22
[gnu-emacs] / lisp / doc-view.el
1 ;;; doc-view.el --- View PDF/PostScript/DVI files in Emacs
2
3 ;; Copyright (C) 2007 Free Software Foundation, Inc.
4 ;;
5 ;; Author: Tassilo Horn <tassilo@member.fsf.org>
6 ;; Maintainer: Tassilo Horn <tassilo@member.fsf.org>
7 ;; Keywords: files, pdf, ps, dvi
8
9 ;; This file is part of GNU Emacs.
10
11 ;; GNU Emacs is free software; you can redistribute it and/or modify
12 ;; it under the terms of the GNU General Public License as published by
13 ;; the Free Software Foundation; either version 3, or (at your option)
14 ;; any later version.
15
16 ;; GNU Emacs is distributed in the hope that it will be useful,
17 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 ;; GNU General Public License for more details.
20
21 ;; You should have received a copy of the GNU General Public License
22 ;; along with GNU Emacs; see the file COPYING. If not, write to the
23 ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
24 ;; Boston, MA 02110-1301, USA.
25
26 ;;; Requirements:
27
28 ;; doc-view.el requires GNU Emacs 22.1 or newer. You also need Ghostscript,
29 ;; `dvipdfm' which comes with teTeX and `pdftotext', which comes with xpdf
30 ;; (http://www.foolabs.com/xpdf/) or poppler (http://poppler.freedesktop.org/).
31
32 ;;; Commentary:
33
34 ;; DocView is a document viewer for Emacs. It converts PDF, PS and DVI files
35 ;; to a set of PNG files, one PNG for each page, and displays the PNG images
36 ;; inside an Emacs buffer. This buffer uses `doc-view-mode' which provides
37 ;; convenient key bindings for browsing the document.
38 ;;
39 ;; To use it simply open a document file with
40 ;;
41 ;; C-x C-f ~/path/to/document RET
42 ;;
43 ;; and the document will be converted and displayed, if your emacs supports png
44 ;; images. With `C-c C-c' you can toggle between the rendered images
45 ;; representation and the source text representation of the document. With
46 ;; `C-c C-e' you can switch to an appropriate editing mode for the document.
47 ;;
48 ;; Since conversion may take some time all the PNG images are cached in a
49 ;; subdirectory of `doc-view-cache-directory' and reused when you want to view
50 ;; that file again. To reconvert a document hit `g' (`doc-view-reconvert-doc')
51 ;; when displaying the document. To delete all cached files use
52 ;; `doc-view-clear-cache'. To open the cache with dired, so that you can tidy
53 ;; it out use `doc-view-dired-cache'.
54 ;;
55 ;; When conversion in underway the first page will be displayed as soon as it
56 ;; is available and the available pages are refreshed every
57 ;; `doc-view-conversion-refresh-interval' seconds. If that variable is nil the
58 ;; pages won't be displayed before conversion of the document finished
59 ;; completely.
60 ;;
61 ;; DocView lets you select a slice of the displayed pages. This slice will be
62 ;; remembered and applied to all pages of the current document. This enables
63 ;; you to cut away the margins of a document to save some space. To select a
64 ;; slice you can use `doc-view-set-slice' (bound to `s s') which will query you
65 ;; for the coordinates of the slice's top-left corner and its width and height.
66 ;; A much more convenient way to do the same is offered by the command
67 ;; `doc-view-set-slice-using-mouse' (bound to `s m'). After invokation you
68 ;; only have to press mouse-1 at the top-left corner and drag it to the
69 ;; bottom-right corner of the desired slice. To reset the slice use
70 ;; `doc-view-reset-slice' (bound to `s r').
71 ;;
72 ;; You can also search within the document. The command `doc-view-search'
73 ;; (bound to `C-s') queries for a search regexp and initializes a list of all
74 ;; matching pages and messages how many match-pages were found. After that you
75 ;; can jump to the next page containing a match with
76 ;; `doc-view-search-next-match' (bound to `C-S-n') or to the previous matching
77 ;; page with `doc-view-search-previous-match' (bound to `C-S-p'). This works
78 ;; by searching a plain text representation of the document. If that doesn't
79 ;; already exist the first invokation of `doc-view-search' starts the
80 ;; conversion. When that finishes and you're still viewing the document
81 ;; (i.e. you didn't switch to another buffer) you're queried for the regexp
82 ;; then.
83 ;;
84 ;; Dired users can simply hit `v' on a document file. If it's a PS, PDF or DVI
85 ;; it will be opened using `doc-view-mode'.
86 ;;
87
88 ;;; Configuration:
89
90 ;; If the images are too small or too big you should set the "-rXXX" option in
91 ;; `doc-view-ghostscript-options' to another value. (The bigger your screen,
92 ;; the higher the value.)
93 ;;
94 ;; This and all other options can be set with the customization interface.
95 ;; Simply do
96 ;;
97 ;; M-x customize-group RET doc-view RET
98 ;;
99 ;; and modify them to your needs.
100
101 ;;; Code:
102
103 (require 'dired)
104 (require 'image-mode)
105 (eval-when-compile (require 'cl))
106
107 ;;;; Customization Options
108
109 (defgroup doc-view nil
110 "In-buffer viewer for PDF, PostScript and DVI files."
111 :link '(function-link doc-view)
112 :version "22.2"
113 :group 'applications
114 :group 'multimedia
115 :prefix "doc-view-")
116
117 (defcustom doc-view-ghostscript-program (executable-find "gs")
118 "Program to convert PS and PDF files to PNG."
119 :type 'file
120 :group 'doc-view)
121
122 (defcustom doc-view-ghostscript-options
123 '("-dSAFER" ;; Avoid security problems when rendering files from untrusted
124 ;; sources.
125 "-dNOPAUSE" "-sDEVICE=png16m" "-dTextAlphaBits=4"
126 "-dBATCH" "-dGraphicsAlphaBits=4" "-dQUIET" "-r100")
127 "A list of options to give to ghostscript."
128 :type '(repeat string)
129 :group 'doc-view)
130
131 (defcustom doc-view-dvipdfm-program (executable-find "dvipdfm")
132 "Program to convert DVI files to PDF.
133
134 DVI file will be converted to PDF before the resulting PDF is
135 converted to PNG."
136 :type 'file
137 :group 'doc-view)
138
139 (defcustom doc-view-ps2pdf-program (executable-find "ps2pdf")
140 "Program to convert PS files to PDF.
141
142 PS files will be converted to PDF before searching is possible."
143 :type 'file
144 :group 'doc-view)
145
146 (defcustom doc-view-pdftotext-program (executable-find "pdftotext")
147 "Program to convert PDF files to plain text.
148
149 Needed for searching."
150 :type 'file
151 :group 'doc-view)
152
153 (defcustom doc-view-cache-directory (concat temporary-file-directory
154 "doc-view")
155 "The base directory, where the PNG images will be saved."
156 :type 'directory
157 :group 'doc-view)
158
159 (defcustom doc-view-conversion-buffer "*doc-view conversion output*"
160 "The buffer where messages from the converter programs go to."
161 :type 'string
162 :group 'doc-view)
163
164 (defcustom doc-view-conversion-refresh-interval 3
165 "Every how much seconds the DocView buffer gets refreshed while conversion.
166 After such an refresh newly converted pages will be available for
167 viewing. If set to nil there won't be any refreshes and the
168 pages won't be displayed before conversion of the whole document
169 has finished."
170 :type 'integer
171 :group 'doc-view)
172
173 ;;;; Internal Variables
174
175 (defvar doc-view-current-files nil
176 "Only used internally.")
177
178 (defvar doc-view-current-page nil
179 "Only used internally.")
180
181 (defvar doc-view-current-doc nil
182 "Only used internally.")
183
184 (defvar doc-view-current-converter-process nil
185 "Only used internally.")
186
187 (defvar doc-view-current-timer nil
188 "Only used internally.")
189
190 (defvar doc-view-current-slice nil
191 "Only used internally.")
192
193 (defvar doc-view-current-cache-dir nil
194 "Only used internally.")
195
196 (defvar doc-view-current-search-matches nil
197 "Only used internally.")
198
199 (defvar doc-view-current-image nil
200 "Only used internally.")
201
202 (defvar doc-view-current-info nil
203 "Only used internally.")
204
205 (defvar doc-view-current-display nil
206 "Only used internally.")
207
208 ;;;; DocView Keymaps
209
210 (defvar doc-view-mode-map
211 (let ((map (make-sparse-keymap)))
212 ;; Navigation in the document
213 (define-key map (kbd "n") 'doc-view-next-page)
214 (define-key map (kbd "p") 'doc-view-previous-page)
215 (define-key map (kbd "<next>") 'doc-view-next-page)
216 (define-key map (kbd "<prior>") 'doc-view-previous-page)
217 (define-key map (kbd "SPC") 'doc-view-scroll-up-or-next-page)
218 (define-key map (kbd "DEL") 'doc-view-scroll-down-or-previous-page)
219 (define-key map (kbd "M-<") 'doc-view-first-page)
220 (define-key map (kbd "M->") 'doc-view-last-page)
221 (define-key map (kbd "g") 'doc-view-goto-page)
222 ;; Killing/burying the buffer (and the process)
223 (define-key map (kbd "q") 'bury-buffer)
224 (define-key map (kbd "k") 'doc-view-kill-proc-and-buffer)
225 ;; Slicing the image
226 (define-key map (kbd "s s") 'doc-view-set-slice)
227 (define-key map (kbd "s m") 'doc-view-set-slice-using-mouse)
228 (define-key map (kbd "s r") 'doc-view-reset-slice)
229 ;; Searching
230 (define-key map (kbd "C-s") 'doc-view-search)
231 (define-key map (kbd "<find>") 'doc-view-search)
232 (define-key map (kbd "C-S-n") 'doc-view-search-next-match)
233 (define-key map (kbd "C-S-p") 'doc-view-search-previous-match)
234 ;; Scrolling
235 (define-key map (kbd "<right>") 'image-forward-hscroll)
236 (define-key map (kbd "<left>") 'image-backward-hscroll)
237 (define-key map (kbd "<down>") 'image-next-line)
238 (define-key map (kbd "<up>") 'image-previous-line)
239 (define-key map (kbd "C-f") 'image-forward-hscroll)
240 (define-key map (kbd "C-b") 'image-backward-hscroll)
241 (define-key map (kbd "C-n") 'image-next-line)
242 (define-key map (kbd "C-p") 'image-previous-line)
243 (define-key map (kbd "C-v") 'scroll-up)
244 (define-key map (kbd "<mouse-4>") 'mwheel-scroll)
245 (define-key map (kbd "<mouse-5>") 'mwheel-scroll)
246 (define-key map (kbd "M-v") 'scroll-down)
247 ;; Show the tooltip
248 (define-key map (kbd "C-t") 'doc-view-show-tooltip)
249 ;; Toggle between text and image display or editing
250 (define-key map (kbd "C-c C-c") 'doc-view-toggle-display)
251 (define-key map (kbd "C-c C-e") 'doc-view-edit-doc)
252 ;; Reconvert the current document
253 (define-key map (kbd "g") 'doc-view-reconvert-doc)
254 (suppress-keymap map)
255 map)
256 "Keymap used by `doc-view-mode' when displaying a doc as a set of images.")
257
258 (defvar doc-view-mode-text-map
259 (let ((map (make-sparse-keymap)))
260 ;; Toggle between text and image display or editing
261 (define-key map (kbd "C-c C-c") 'doc-view-toggle-display)
262 (define-key map (kbd "C-c C-e") 'doc-view-edit-doc)
263 ;; Killing/burying the buffer (and the process)
264 (define-key map (kbd "q") 'bury-buffer)
265 (define-key map (kbd "k") 'doc-view-kill-proc-and-buffer)
266 (define-key map (kbd "C-x k") 'doc-view-kill-proc-and-buffer)
267 map)
268 "Keymap used by `doc-view-mode' when displaying a document as text.")
269
270 ;;;; Navigation Commands
271
272 (defun doc-view-goto-page (page)
273 "View the page given by PAGE."
274 (interactive "nPage: ")
275 (let ((len (length doc-view-current-files)))
276 (if (< page 1)
277 (setq page 1)
278 (when (> page len)
279 (setq page len)))
280 (setq doc-view-current-page page
281 doc-view-current-info
282 (concat
283 (propertize
284 (format "Page %d of %d."
285 doc-view-current-page
286 len) 'face 'bold)
287 ;; Tell user if converting isn't finished yet
288 (if doc-view-current-converter-process
289 " (still converting...)\n"
290 "\n")
291 ;; Display context infos if this page matches the last search
292 (when (and doc-view-current-search-matches
293 (assq doc-view-current-page
294 doc-view-current-search-matches))
295 (concat (propertize "Search matches:\n" 'face 'bold)
296 (let ((contexts ""))
297 (dolist (m (cdr (assq doc-view-current-page
298 doc-view-current-search-matches)))
299 (setq contexts (concat contexts " - \"" m "\"\n")))
300 contexts)))))
301 ;; Update the buffer
302 (let ((inhibit-read-only t))
303 (erase-buffer)
304 (let ((beg (point)))
305 (doc-view-insert-image (nth (1- page) doc-view-current-files)
306 :pointer 'arrow)
307 (put-text-property beg (point) 'help-echo doc-view-current-info))
308 (insert "\n" doc-view-current-info)
309 (goto-char (point-min))
310 (forward-char))
311 (set-buffer-modified-p nil)))
312
313 (defun doc-view-next-page (&optional arg)
314 "Browse ARG pages forward."
315 (interactive "p")
316 (doc-view-goto-page (+ doc-view-current-page (or arg 1))))
317
318 (defun doc-view-previous-page (&optional arg)
319 "Browse ARG pages backward."
320 (interactive "p")
321 (doc-view-goto-page (- doc-view-current-page (or arg 1))))
322
323 (defun doc-view-first-page ()
324 "View the first page."
325 (interactive)
326 (doc-view-goto-page 1))
327
328 (defun doc-view-last-page ()
329 "View the last page."
330 (interactive)
331 (doc-view-goto-page (length doc-view-current-files)))
332
333 (defun doc-view-scroll-up-or-next-page ()
334 "Scroll page up if possible, else goto next page."
335 (interactive)
336 (condition-case nil
337 (scroll-up)
338 (error (doc-view-next-page))))
339
340 (defun doc-view-scroll-down-or-previous-page ()
341 "Scroll page down if possible, else goto previous page."
342 (interactive)
343 (condition-case nil
344 (scroll-down)
345 (error (doc-view-previous-page)
346 (goto-char (point-max)))))
347
348 (defun doc-view-kill-proc ()
349 "Kill the current converter process."
350 (interactive)
351 (when doc-view-current-converter-process
352 (kill-process doc-view-current-converter-process))
353 (when doc-view-current-timer
354 (cancel-timer doc-view-current-timer)
355 (setq doc-view-current-timer nil))
356 (setq mode-line-process nil))
357
358 (defun doc-view-kill-proc-and-buffer ()
359 "Kill the current converter process and buffer."
360 (interactive)
361 (doc-view-kill-proc)
362 (when (eq major-mode 'doc-view-mode)
363 (kill-buffer (current-buffer))))
364
365 ;;;; Conversion Functions
366
367 (defun doc-view-reconvert-doc (&rest args)
368 "Reconvert the current document.
369 Should be invoked when the cached images aren't up-to-date."
370 (interactive)
371 (let ((inhibit-read-only t)
372 (doc doc-view-current-doc))
373 (doc-view-kill-proc)
374 ;; Clear the old cached files
375 (when (file-exists-p (doc-view-current-cache-dir))
376 (dired-delete-file (doc-view-current-cache-dir) 'always))
377 (doc-view-kill-proc-and-buffer)
378 (find-file doc)))
379
380 (defun doc-view-current-cache-dir ()
381 "Return the directory where the png files of the current doc should be saved.
382 It's a subdirectory of `doc-view-cache-directory'."
383 (if doc-view-current-cache-dir
384 doc-view-current-cache-dir
385 (setq doc-view-current-cache-dir
386 (file-name-as-directory
387 (concat (file-name-as-directory doc-view-cache-directory)
388 (let ((doc doc-view-current-doc))
389 (with-temp-buffer
390 (insert-file-contents-literally doc)
391 (md5 (current-buffer)))))))))
392
393 (defun doc-view-dvi->pdf-sentinel (proc event)
394 "If DVI->PDF conversion was successful, convert the PDF to PNG now."
395 (if (not (string-match "finished" event))
396 (message "DocView: dvi->pdf process changed status to %s." event)
397 (set-buffer (process-get proc 'buffer))
398 (setq doc-view-current-converter-process nil
399 mode-line-process nil)
400 ;; Now go on converting this PDF to a set of PNG files.
401 (let* ((pdf (process-get proc 'pdf-file))
402 (png (concat (doc-view-current-cache-dir)
403 "page-%d.png")))
404 (doc-view-pdf/ps->png pdf png))))
405
406 (defun doc-view-dvi->pdf (dvi pdf)
407 "Convert DVI to PDF asynchrounously."
408 (setq doc-view-current-converter-process
409 (start-process "dvi->pdf" doc-view-conversion-buffer
410 doc-view-dvipdfm-program
411 "-o" pdf dvi)
412 mode-line-process (list (format ":%s" doc-view-current-converter-process)))
413 (set-process-sentinel doc-view-current-converter-process
414 'doc-view-dvi->pdf-sentinel)
415 (process-put doc-view-current-converter-process 'buffer (current-buffer))
416 (process-put doc-view-current-converter-process 'pdf-file pdf))
417
418 (defun doc-view-pdf/ps->png-sentinel (proc event)
419 "If PDF/PS->PNG conversion was successful, update the display."
420 (if (not (string-match "finished" event))
421 (message "DocView: converter process changed status to %s." event)
422 (set-buffer (process-get proc 'buffer))
423 (setq doc-view-current-converter-process nil
424 mode-line-process nil)
425 (when doc-view-current-timer
426 (cancel-timer doc-view-current-timer)
427 (setq doc-view-current-timer nil))
428 ;; Yippie, finished. Update the display!
429 (doc-view-display doc-view-current-doc)))
430
431 (defun doc-view-pdf/ps->png (pdf-ps png)
432 "Convert PDF-PS to PNG asynchrounously."
433 (setq doc-view-current-converter-process
434 (apply 'start-process
435 (append (list "pdf/ps->png" doc-view-conversion-buffer
436 doc-view-ghostscript-program)
437 doc-view-ghostscript-options
438 (list (concat "-sOutputFile=" png))
439 (list pdf-ps)))
440 mode-line-process (list (format ":%s" doc-view-current-converter-process)))
441 (process-put doc-view-current-converter-process
442 'buffer (current-buffer))
443 (set-process-sentinel doc-view-current-converter-process
444 'doc-view-pdf/ps->png-sentinel)
445 (when doc-view-conversion-refresh-interval
446 (setq doc-view-current-timer
447 (run-at-time "1 secs" doc-view-conversion-refresh-interval
448 'doc-view-display-maybe
449 doc-view-current-doc))))
450
451 (defun doc-view-pdf->txt-sentinel (proc event)
452 (if (not (string-match "finished" event))
453 (message "DocView: converter process changed status to %s." event)
454 (let ((current-buffer (current-buffer))
455 (proc-buffer (process-get proc 'buffer)))
456 (set-buffer proc-buffer)
457 (setq doc-view-current-converter-process nil
458 mode-line-process nil)
459 ;; If the user looks at the DocView buffer where the conversion was
460 ;; performed, search anew. This time it will be queried for a regexp.
461 (when (eq current-buffer proc-buffer)
462 (doc-view-search)))))
463
464 (defun doc-view-pdf->txt (pdf txt)
465 "Convert PDF to TXT asynchrounously."
466 (setq doc-view-current-converter-process
467 (start-process "pdf->txt" doc-view-conversion-buffer
468 doc-view-pdftotext-program "-raw"
469 pdf txt)
470 mode-line-process (list (format ":%s" doc-view-current-converter-process)))
471 (set-process-sentinel doc-view-current-converter-process
472 'doc-view-pdf->txt-sentinel)
473 (process-put doc-view-current-converter-process 'buffer (current-buffer)))
474
475 (defun doc-view-ps->pdf-sentinel (proc event)
476 (if (not (string-match "finished" event))
477 (message "DocView: converter process changed status to %s." event)
478 (set-buffer (process-get proc 'buffer))
479 (setq doc-view-current-converter-process nil
480 mode-line-process nil)
481 ;; Now we can transform to plain text.
482 (doc-view-pdf->txt (process-get proc 'pdf-file)
483 (concat (doc-view-current-cache-dir)
484 "doc.txt"))))
485
486 (defun doc-view-ps->pdf (ps pdf)
487 "Convert PS to PDF asynchronously."
488 (setq doc-view-current-converter-process
489 (start-process "ps->pdf" doc-view-conversion-buffer
490 doc-view-ps2pdf-program
491 ps pdf
492 ;; Avoid security problems when rendering files from
493 ;; untrusted sources.
494 "-dSAFER")
495 mode-line-process (list (format ":%s" doc-view-current-converter-process)))
496 (set-process-sentinel doc-view-current-converter-process
497 'doc-view-ps->pdf-sentinel)
498 (process-put doc-view-current-converter-process 'buffer (current-buffer))
499 (process-put doc-view-current-converter-process 'pdf-file pdf))
500
501 (defun doc-view-convert-current-doc ()
502 "Convert `doc-view-current-doc' to a set of png files, one file per page.
503 Those files are saved in the directory given by the function
504 `doc-view-current-cache-dir'."
505 (clear-image-cache)
506 (let ((png-file (concat (doc-view-current-cache-dir)
507 "page-%d.png")))
508 (make-directory doc-view-current-cache-dir t)
509 (if (not (string= (file-name-extension doc-view-current-doc) "dvi"))
510 ;; Convert to PNG images.
511 (doc-view-pdf/ps->png doc-view-current-doc png-file)
512 ;; DVI files have to be converted to PDF before Ghostscript can process
513 ;; it.
514 (doc-view-dvi->pdf doc-view-current-doc
515 (concat (file-name-as-directory doc-view-current-cache-dir)
516 "doc.pdf")))))
517
518 ;;;; Slicing
519
520 (defun doc-view-set-slice (x y width height)
521 "Set the slice of the images that should be displayed.
522 You can use this function to tell doc-view not to display the
523 margins of the document. It prompts for the top-left corner (X
524 and Y) of the slice to display and its WIDTH and HEIGHT.
525
526 See `doc-view-set-slice-using-mouse' for a more convenient way to
527 do that. To reset the slice use `doc-view-reset-slice'."
528 (interactive
529 (let* ((size (image-size doc-view-current-image t))
530 (a (read-number (format "Top-left X (0..%d): " (car size))))
531 (b (read-number (format "Top-left Y (0..%d): " (cdr size))))
532 (c (read-number (format "Width (0..%d): " (- (car size) a))))
533 (d (read-number (format "Height (0..%d): " (- (cdr size) b)))))
534 (list a b c d)))
535 (setq doc-view-current-slice (list x y width height))
536 ;; Redisplay
537 (doc-view-goto-page doc-view-current-page))
538
539 (defun doc-view-set-slice-using-mouse ()
540 "Set the slice of the images that should be displayed.
541 You set the slice by pressing mouse-1 at its top-left corner and
542 dragging it to its bottom-right corner. See also
543 `doc-view-set-slice' and `doc-view-reset-slice'."
544 (interactive)
545 (let (x y w h done)
546 (while (not done)
547 (let ((e (read-event
548 (concat "Press mouse-1 at the top-left corner and "
549 "drag it to the bottom-right corner!"))))
550 (when (eq (car e) 'drag-mouse-1)
551 (setq x (car (posn-object-x-y (event-start e))))
552 (setq y (cdr (posn-object-x-y (event-start e))))
553 (setq w (- (car (posn-object-x-y (event-end e))) x))
554 (setq h (- (cdr (posn-object-x-y (event-end e))) y))
555 (setq done t))))
556 (doc-view-set-slice x y w h)))
557
558 (defun doc-view-reset-slice ()
559 "Reset the current slice.
560 After calling this function the whole pages will be visible
561 again."
562 (interactive)
563 (setq doc-view-current-slice nil)
564 ;; Redisplay
565 (doc-view-goto-page doc-view-current-page))
566
567 ;;;; Display
568
569 (defun doc-view-insert-image (file &rest args)
570 "Insert the given png FILE.
571 ARGS is a list of image descriptors."
572 (let ((image (apply 'create-image file 'png nil args)))
573 (setq doc-view-current-image image)
574 (insert-image image (concat "[" file "]") nil doc-view-current-slice)))
575
576 (defun doc-view-sort (a b)
577 "Return non-nil if A should be sorted before B.
578 Predicate for sorting `doc-view-current-files'."
579 (if (< (length a) (length b))
580 t
581 (if (> (length a) (length b))
582 nil
583 (string< a b))))
584
585 (defun doc-view-display-maybe (doc)
586 "Call `doc-view-display' iff we're in the image display."
587 (when (eq doc-view-current-display 'image)
588 (doc-view-display doc)))
589
590 (defun doc-view-display (doc)
591 "Start viewing the document DOC."
592 (set-buffer (get-file-buffer doc))
593 (setq doc-view-current-files
594 (sort (directory-files (doc-view-current-cache-dir) t
595 "page-[0-9]+\\.png" t)
596 'doc-view-sort))
597 (when (> (length doc-view-current-files) 0)
598 (doc-view-goto-page doc-view-current-page)))
599
600 (defun doc-view-buffer-message ()
601 (insert (propertize "Welcome to DocView!" 'face 'bold)
602 "\n"
603 "
604 If you see this buffer it means that the document you want to
605 view gets converted to PNG now and the conversion of the first
606 page hasn't finished yet or
607 `doc-view-conversion-refresh-interval' is set to nil.
608
609 For now these keys are useful:
610
611 `q' : Bury this buffer. Conversion will go on in background.
612 `k' : Kill the conversion process and this buffer.\n")
613 (set-buffer-modified-p nil))
614
615 (defun doc-view-show-tooltip ()
616 (interactive)
617 (tooltip-show doc-view-current-info))
618
619 ;;;;; Toggle between text and image display
620
621 (defun doc-view-toggle-display ()
622 "Start or stop displaying a document file as a set of images.
623 This command toggles between showing the text of the document
624 file and showing the document as a set of images."
625 (interactive)
626 (if (get-text-property (point-min) 'display)
627 ;; Switch to text display
628 (let ((inhibit-read-only t))
629 (erase-buffer)
630 (insert-file-contents doc-view-current-doc)
631 (use-local-map doc-view-mode-text-map)
632 (setq mode-name "DocView[text]"
633 doc-view-current-display 'text)
634 (if (called-interactively-p)
635 (message "Repeat this command to go back to displaying the file as images")))
636 ;; Switch to image display
637 (let ((inhibit-read-only t))
638 (erase-buffer)
639 (doc-view-buffer-message)
640 (setq doc-view-current-page (or doc-view-current-page 1))
641 (if (file-exists-p (doc-view-current-cache-dir))
642 (progn
643 (message "DocView: using cached files!")
644 (doc-view-display doc-view-current-doc))
645 (doc-view-convert-current-doc))
646 (use-local-map doc-view-mode-map)
647 (setq mode-name (format "DocView")
648 doc-view-current-display 'image)
649 (if (called-interactively-p)
650 (message "Repeat this command to go back to displaying the file as text"))))
651 (set-buffer-modified-p nil))
652
653 ;;;;; Leave doc-view-mode and open the file for edit
654
655 (defun doc-view-edit-doc ()
656 "Leave `doc-view-mode' and open the current doc with an appropriate editing mode."
657 (interactive)
658 (let ((filename doc-view-current-doc)
659 (auto-mode-alist (append '(("\\.[eE]?[pP][sS]\\'" . ps-mode)
660 ("\\.\\(pdf\\|PDF\\|dvi\\|DVI\\)$" . fundamental-mode))
661 auto-mode-alist)))
662 (kill-buffer (current-buffer))
663 (find-file filename)))
664
665 ;;;; Searching
666
667 (defun doc-view-search-internal (regexp file)
668 "Return a list of FILE's pages that contain text matching REGEXP.
669 The value is an alist of the form (PAGE CONTEXTS) where PAGE is
670 the pagenumber and CONTEXTS are all lines of text containing a match."
671 (with-temp-buffer
672 (insert-file-contents file)
673 (let ((page 1)
674 (lastpage 1)
675 matches)
676 (while (re-search-forward (concat "\\(?:\\([\f]\\)\\|\\("
677 regexp "\\)\\)") nil t)
678 (when (match-string 1) (incf page))
679 (when (match-string 2)
680 (if (/= page lastpage)
681 (setq matches (push (cons page
682 (list (buffer-substring
683 (line-beginning-position)
684 (line-end-position))))
685 matches))
686 (setq matches (cons
687 (append
688 (or
689 ;; This page already is a match.
690 (car matches)
691 ;; This is the first match on page.
692 (list page))
693 (list (buffer-substring
694 (line-beginning-position)
695 (line-end-position))))
696 (cdr matches))))
697 (setq lastpage page)))
698 (nreverse matches))))
699
700 (defun doc-view-search-no-of-matches (list)
701 "Extract the number of matches from the search result LIST."
702 (let ((no 0))
703 (dolist (p list)
704 (setq no (+ no (1- (length p)))))
705 no))
706
707 (defun doc-view-search ()
708 "Query for a regexp and search the current document.
709 If the current document hasn't been transformed to plain text
710 till now do that first. You should try searching anew when the
711 conversion finished."
712 (interactive)
713 ;; New search, so forget the old results.
714 (setq doc-view-current-search-matches nil)
715 (let ((txt (concat (doc-view-current-cache-dir)
716 "doc.txt")))
717 (if (file-readable-p txt)
718 (progn
719 (setq doc-view-current-search-matches
720 (doc-view-search-internal
721 (read-from-minibuffer "Regexp: ")
722 txt))
723 (message "DocView: search yielded %d matches."
724 (doc-view-search-no-of-matches
725 doc-view-current-search-matches)))
726 ;; We must convert to TXT first!
727 (if doc-view-current-converter-process
728 (message "DocView: please wait till conversion finished.")
729 (let ((ext (file-name-extension doc-view-current-doc)))
730 (cond
731 ((string= ext "pdf")
732 ;; Doc is a PDF, so convert it to TXT
733 (doc-view-pdf->txt doc-view-current-doc txt))
734 ((string= ext "ps")
735 ;; Doc is a PS, so convert it to PDF (which will be converted to
736 ;; TXT thereafter).
737 (doc-view-ps->pdf doc-view-current-doc
738 (concat (doc-view-current-cache-dir)
739 "doc.pdf")))
740 ((string= ext "dvi")
741 ;; Doc is a DVI. This means that a doc.pdf already exists in its
742 ;; cache subdirectory.
743 (doc-view-pdf->txt (concat (doc-view-current-cache-dir)
744 "doc.pdf")
745 txt))
746 (t (error "DocView doesn't know what to do"))))))))
747
748 (defun doc-view-search-next-match (arg)
749 "Go to the ARGth next matching page."
750 (interactive "p")
751 (let* ((next-pages (remove-if (lambda (i) (<= (car i) doc-view-current-page))
752 doc-view-current-search-matches))
753 (page (car (nth (1- arg) next-pages))))
754 (if page
755 (doc-view-goto-page page)
756 (when (and
757 doc-view-current-search-matches
758 (y-or-n-p "No more matches after current page. Wrap to first match? "))
759 (doc-view-goto-page (caar doc-view-current-search-matches))))))
760
761 (defun doc-view-search-previous-match (arg)
762 "Go to the ARGth previous matching page."
763 (interactive "p")
764 (let* ((prev-pages (remove-if (lambda (i) (>= (car i) doc-view-current-page))
765 doc-view-current-search-matches))
766 (page (car (nth (1- arg) (nreverse prev-pages)))))
767 (if page
768 (doc-view-goto-page page)
769 (when (and
770 doc-view-current-search-matches
771 (y-or-n-p "No more matches before current page. Wrap to last match? "))
772 (doc-view-goto-page (caar (last doc-view-current-search-matches)))))))
773
774 ;;;; User interface commands and the mode
775
776 (put 'doc-view-mode 'mode-class 'special)
777
778 ;;;###autoload
779 (define-derived-mode doc-view-mode nil "DocView"
780 "Major mode in DocView buffers.
781 You can use \\<doc-view-mode-map>\\[doc-view-toggle-display] to
782 toggle between display as a set of images and display as text."
783 :group 'doc-view
784 (make-local-variable 'doc-view-current-files)
785 (make-local-variable 'doc-view-current-doc)
786 (make-local-variable 'doc-view-current-image)
787 (make-local-variable 'doc-view-current-page)
788 (make-local-variable 'doc-view-current-converter-process)
789 (make-local-variable 'doc-view-current-timer)
790 (make-local-variable 'doc-view-current-slice)
791 (make-local-variable 'doc-view-current-cache-dir)
792 (make-local-variable 'doc-view-current-info)
793 (make-local-variable 'doc-view-current-search-matches)
794 (setq doc-view-current-doc (buffer-file-name))
795 (insert-file-contents doc-view-current-doc)
796 (use-local-map doc-view-mode-text-map)
797 (setq mode-name "DocView[text]"
798 doc-view-current-display 'text
799 buffer-read-only t
800 revert-buffer-function 'doc-view-reconvert-doc)
801 ;; Switch to image display if possible
802 (if (and (display-images-p)
803 (image-type-available-p 'png)
804 (not (get-text-property (point-min) 'display)))
805 (doc-view-toggle-display))
806 (message
807 "%s"
808 (substitute-command-keys
809 "Type \\[doc-view-toggle-display] to toggle between image and text display.")))
810
811 (defun doc-view-clear-cache ()
812 "Delete the whole cache (`doc-view-cache-directory')."
813 (interactive)
814 (dired-delete-file doc-view-cache-directory 'always)
815 (make-directory doc-view-cache-directory))
816
817 (defun doc-view-dired-cache ()
818 "Open `dired' in `doc-view-cache-directory'."
819 (interactive)
820 (dired doc-view-cache-directory))
821
822 (provide 'doc-view)
823
824 ;; Local Variables:
825 ;; mode: outline-minor
826 ;; End:
827
828 ;; arch-tag: 5d6e5c5e-095f-489e-b4e4-1ca90a7d79be
829 ;;; doc-view.el ends here