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