1 ;;; doc-view.el --- View PDF/PostScript/DVI files in Emacs
3 ;; Copyright (C) 2007 Free Software Foundation, Inc.
5 ;; Author: Tassilo Horn <tassilo@member.fsf.org>
6 ;; Maintainer: Tassilo Horn <tassilo@member.fsf.org>
7 ;; Keywords: files, pdf, ps, dvi
9 ;; This file is part of GNU Emacs.
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)
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.
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.
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/).
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.
39 ;; To use it simply open a document file with
41 ;; C-x C-f ~/path/to/document RET
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.
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'.
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
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').
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
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'.
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.)
94 ;; This and all other options can be set with the customization interface.
97 ;; M-x customize-group RET doc-view RET
99 ;; and modify them to your needs.
104 (require 'image-mode)
105 (eval-when-compile (require 'cl))
107 ;;;; Customization Options
109 (defgroup doc-view nil
110 "In-buffer viewer for PDF, PostScript and DVI files."
111 :link '(function-link doc-view)
117 (defcustom doc-view-ghostscript-program (executable-find "gs")
118 "Program to convert PS and PDF files to PNG."
122 (defcustom doc-view-ghostscript-options
123 '("-dSAFER" ;; Avoid security problems when rendering files from untrusted
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)
131 (defcustom doc-view-dvipdfm-program (executable-find "dvipdfm")
132 "Program to convert DVI files to PDF.
134 DVI file will be converted to PDF before the resulting PDF is
139 (defcustom doc-view-ps2pdf-program (executable-find "ps2pdf")
140 "Program to convert PS files to PDF.
142 PS files will be converted to PDF before searching is possible."
146 (defcustom doc-view-pdftotext-program (executable-find "pdftotext")
147 "Program to convert PDF files to plain text.
149 Needed for searching."
153 (defcustom doc-view-cache-directory (concat temporary-file-directory
155 "The base directory, where the PNG images will be saved."
159 (defcustom doc-view-conversion-buffer "*doc-view conversion output*"
160 "The buffer where messages from the converter programs go to."
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
173 ;;;; Internal Variables
175 (defvar doc-view-current-files nil
176 "Only used internally.")
178 (defvar doc-view-current-page nil
179 "Only used internally.")
181 (defvar doc-view-current-doc nil
182 "Only used internally.")
184 (defvar doc-view-current-converter-process nil
185 "Only used internally.")
187 (defvar doc-view-current-timer nil
188 "Only used internally.")
190 (defvar doc-view-current-slice nil
191 "Only used internally.")
193 (defvar doc-view-current-cache-dir nil
194 "Only used internally.")
196 (defvar doc-view-current-search-matches nil
197 "Only used internally.")
199 (defvar doc-view-current-image nil
200 "Only used internally.")
202 (defvar doc-view-current-info nil
203 "Only used internally.")
205 (defvar doc-view-current-display nil
206 "Only used internally.")
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>") 'forward-page)
216 (define-key map (kbd "<prior>") 'backward-page)
217 (define-key map [remap forward-page] 'doc-view-next-page)
218 (define-key map [remap backward-page] 'doc-view-previous-page)
219 (define-key map (kbd "SPC") 'doc-view-scroll-up-or-next-page)
220 (define-key map (kbd "DEL") 'doc-view-scroll-down-or-previous-page)
221 (define-key map (kbd "M-<") 'doc-view-first-page)
222 (define-key map (kbd "M->") 'doc-view-last-page)
223 (define-key map (kbd "g") 'doc-view-goto-page)
224 ;; Killing/burying the buffer (and the process)
225 (define-key map (kbd "q") 'bury-buffer)
226 (define-key map (kbd "k") 'doc-view-kill-proc-and-buffer)
228 (define-key map (kbd "s s") 'doc-view-set-slice)
229 (define-key map (kbd "s m") 'doc-view-set-slice-using-mouse)
230 (define-key map (kbd "s r") 'doc-view-reset-slice)
232 (define-key map (kbd "C-s") 'doc-view-search)
233 (define-key map (kbd "<find>") 'doc-view-search)
234 (define-key map (kbd "C-S-n") 'doc-view-search-next-match)
235 (define-key map (kbd "C-S-p") 'doc-view-search-previous-match)
237 (define-key map [remap forward-char] 'image-forward-hscroll)
238 (define-key map [remap backward-char] 'image-backward-hscroll)
239 (define-key map [remap next-line] 'image-next-line)
240 (define-key map [remap previous-line] 'image-previous-line)
242 (define-key map (kbd "C-t") 'doc-view-show-tooltip)
243 ;; Toggle between text and image display or editing
244 (define-key map (kbd "C-c C-c") 'doc-view-toggle-display)
245 (define-key map (kbd "C-c C-e") 'doc-view-edit-doc)
246 ;; Reconvert the current document
247 (define-key map (kbd "g") 'doc-view-reconvert-doc)
248 (suppress-keymap map)
250 "Keymap used by `doc-view-mode' when displaying a doc as a set of images.")
252 (defvar doc-view-mode-text-map
253 (let ((map (make-sparse-keymap)))
254 ;; Toggle between text and image display or editing
255 (define-key map (kbd "C-c C-c") 'doc-view-toggle-display)
256 (define-key map (kbd "C-c C-e") 'doc-view-edit-doc)
257 ;; Killing/burying the buffer (and the process)
258 (define-key map (kbd "q") 'bury-buffer)
259 (define-key map (kbd "k") 'doc-view-kill-proc-and-buffer)
260 (define-key map (kbd "C-x k") 'doc-view-kill-proc-and-buffer)
262 "Keymap used by `doc-view-mode' when displaying a document as text.")
264 ;;;; Navigation Commands
266 (defun doc-view-goto-page (page)
267 "View the page given by PAGE."
268 (interactive "nPage: ")
269 (let ((len (length doc-view-current-files)))
274 (setq doc-view-current-page page
275 doc-view-current-info
278 (format "Page %d of %d."
279 doc-view-current-page
281 ;; Tell user if converting isn't finished yet
282 (if doc-view-current-converter-process
283 " (still converting...)\n"
285 ;; Display context infos if this page matches the last search
286 (when (and doc-view-current-search-matches
287 (assq doc-view-current-page
288 doc-view-current-search-matches))
289 (concat (propertize "Search matches:\n" 'face 'bold)
291 (dolist (m (cdr (assq doc-view-current-page
292 doc-view-current-search-matches)))
293 (setq contexts (concat contexts " - \"" m "\"\n")))
296 (let ((inhibit-read-only t))
299 (doc-view-insert-image (nth (1- page) doc-view-current-files)
301 (put-text-property beg (point) 'help-echo doc-view-current-info))
302 (insert "\n" doc-view-current-info)
303 (goto-char (point-min))
305 (set-buffer-modified-p nil)))
307 (defun doc-view-next-page (&optional arg)
308 "Browse ARG pages forward."
310 (doc-view-goto-page (+ doc-view-current-page (or arg 1))))
312 (defun doc-view-previous-page (&optional arg)
313 "Browse ARG pages backward."
315 (doc-view-goto-page (- doc-view-current-page (or arg 1))))
317 (defun doc-view-first-page ()
318 "View the first page."
320 (doc-view-goto-page 1))
322 (defun doc-view-last-page ()
323 "View the last page."
325 (doc-view-goto-page (length doc-view-current-files)))
327 (defun doc-view-scroll-up-or-next-page ()
328 "Scroll page up if possible, else goto next page."
332 (error (doc-view-next-page))))
334 (defun doc-view-scroll-down-or-previous-page ()
335 "Scroll page down if possible, else goto previous page."
339 (error (doc-view-previous-page)
340 (goto-char (point-max)))))
342 (defun doc-view-kill-proc ()
343 "Kill the current converter process."
345 (when doc-view-current-converter-process
346 (kill-process doc-view-current-converter-process))
347 (when doc-view-current-timer
348 (cancel-timer doc-view-current-timer)
349 (setq doc-view-current-timer nil))
350 (setq mode-line-process nil))
352 (defun doc-view-kill-proc-and-buffer ()
353 "Kill the current converter process and buffer."
356 (when (eq major-mode 'doc-view-mode)
357 (kill-buffer (current-buffer))))
359 ;;;; Conversion Functions
361 (defun doc-view-reconvert-doc (&rest args)
362 "Reconvert the current document.
363 Should be invoked when the cached images aren't up-to-date."
365 (let ((inhibit-read-only t)
366 (doc doc-view-current-doc))
368 ;; Clear the old cached files
369 (when (file-exists-p (doc-view-current-cache-dir))
370 (dired-delete-file (doc-view-current-cache-dir) 'always))
371 (doc-view-kill-proc-and-buffer)
374 (defun doc-view-current-cache-dir ()
375 "Return the directory where the png files of the current doc should be saved.
376 It's a subdirectory of `doc-view-cache-directory'."
377 (if doc-view-current-cache-dir
378 doc-view-current-cache-dir
379 (setq doc-view-current-cache-dir
380 (file-name-as-directory
381 (concat (file-name-as-directory doc-view-cache-directory)
382 (let ((doc doc-view-current-doc))
384 (insert-file-contents-literally doc)
385 (md5 (current-buffer)))))))))
387 (defun doc-view-dvi->pdf-sentinel (proc event)
388 "If DVI->PDF conversion was successful, convert the PDF to PNG now."
389 (if (not (string-match "finished" event))
390 (message "DocView: dvi->pdf process changed status to %s." event)
391 (set-buffer (process-get proc 'buffer))
392 (setq doc-view-current-converter-process nil
393 mode-line-process nil)
394 ;; Now go on converting this PDF to a set of PNG files.
395 (let* ((pdf (process-get proc 'pdf-file))
396 (png (concat (doc-view-current-cache-dir)
398 (doc-view-pdf/ps->png pdf png))))
400 (defun doc-view-dvi->pdf (dvi pdf)
401 "Convert DVI to PDF asynchrounously."
402 (setq doc-view-current-converter-process
403 (start-process "dvi->pdf" doc-view-conversion-buffer
404 doc-view-dvipdfm-program
406 mode-line-process (list (format ":%s" doc-view-current-converter-process)))
407 (set-process-sentinel doc-view-current-converter-process
408 'doc-view-dvi->pdf-sentinel)
409 (process-put doc-view-current-converter-process 'buffer (current-buffer))
410 (process-put doc-view-current-converter-process 'pdf-file pdf))
412 (defun doc-view-pdf/ps->png-sentinel (proc event)
413 "If PDF/PS->PNG conversion was successful, update the display."
414 (if (not (string-match "finished" event))
415 (message "DocView: converter process changed status to %s." event)
416 (set-buffer (process-get proc 'buffer))
417 (setq doc-view-current-converter-process nil
418 mode-line-process nil)
419 (when doc-view-current-timer
420 (cancel-timer doc-view-current-timer)
421 (setq doc-view-current-timer nil))
422 ;; Yippie, finished. Update the display!
423 (doc-view-display doc-view-current-doc)))
425 (defun doc-view-pdf/ps->png (pdf-ps png)
426 "Convert PDF-PS to PNG asynchrounously."
427 (setq doc-view-current-converter-process
428 (apply 'start-process
429 (append (list "pdf/ps->png" doc-view-conversion-buffer
430 doc-view-ghostscript-program)
431 doc-view-ghostscript-options
432 (list (concat "-sOutputFile=" png))
434 mode-line-process (list (format ":%s" doc-view-current-converter-process)))
435 (process-put doc-view-current-converter-process
436 'buffer (current-buffer))
437 (set-process-sentinel doc-view-current-converter-process
438 'doc-view-pdf/ps->png-sentinel)
439 (when doc-view-conversion-refresh-interval
440 (setq doc-view-current-timer
441 (run-at-time "1 secs" doc-view-conversion-refresh-interval
442 'doc-view-display-maybe
443 doc-view-current-doc))))
445 (defun doc-view-pdf->txt-sentinel (proc event)
446 (if (not (string-match "finished" event))
447 (message "DocView: converter process changed status to %s." event)
448 (let ((current-buffer (current-buffer))
449 (proc-buffer (process-get proc 'buffer)))
450 (set-buffer proc-buffer)
451 (setq doc-view-current-converter-process nil
452 mode-line-process nil)
453 ;; If the user looks at the DocView buffer where the conversion was
454 ;; performed, search anew. This time it will be queried for a regexp.
455 (when (eq current-buffer proc-buffer)
456 (doc-view-search)))))
458 (defun doc-view-pdf->txt (pdf txt)
459 "Convert PDF to TXT asynchrounously."
460 (setq doc-view-current-converter-process
461 (start-process "pdf->txt" doc-view-conversion-buffer
462 doc-view-pdftotext-program "-raw"
464 mode-line-process (list (format ":%s" doc-view-current-converter-process)))
465 (set-process-sentinel doc-view-current-converter-process
466 'doc-view-pdf->txt-sentinel)
467 (process-put doc-view-current-converter-process 'buffer (current-buffer)))
469 (defun doc-view-ps->pdf-sentinel (proc event)
470 (if (not (string-match "finished" event))
471 (message "DocView: converter process changed status to %s." event)
472 (set-buffer (process-get proc 'buffer))
473 (setq doc-view-current-converter-process nil
474 mode-line-process nil)
475 ;; Now we can transform to plain text.
476 (doc-view-pdf->txt (process-get proc 'pdf-file)
477 (concat (doc-view-current-cache-dir)
480 (defun doc-view-ps->pdf (ps pdf)
481 "Convert PS to PDF asynchronously."
482 (setq doc-view-current-converter-process
483 (start-process "ps->pdf" doc-view-conversion-buffer
484 doc-view-ps2pdf-program
486 ;; Avoid security problems when rendering files from
487 ;; untrusted sources.
489 mode-line-process (list (format ":%s" doc-view-current-converter-process)))
490 (set-process-sentinel doc-view-current-converter-process
491 'doc-view-ps->pdf-sentinel)
492 (process-put doc-view-current-converter-process 'buffer (current-buffer))
493 (process-put doc-view-current-converter-process 'pdf-file pdf))
495 (defun doc-view-convert-current-doc ()
496 "Convert `doc-view-current-doc' to a set of png files, one file per page.
497 Those files are saved in the directory given by the function
498 `doc-view-current-cache-dir'."
500 (let ((png-file (concat (doc-view-current-cache-dir)
502 (make-directory doc-view-current-cache-dir t)
503 (if (not (string= (file-name-extension doc-view-current-doc) "dvi"))
504 ;; Convert to PNG images.
505 (doc-view-pdf/ps->png doc-view-current-doc png-file)
506 ;; DVI files have to be converted to PDF before Ghostscript can process
508 (doc-view-dvi->pdf doc-view-current-doc
509 (concat (file-name-as-directory doc-view-current-cache-dir)
514 (defun doc-view-set-slice (x y width height)
515 "Set the slice of the images that should be displayed.
516 You can use this function to tell doc-view not to display the
517 margins of the document. It prompts for the top-left corner (X
518 and Y) of the slice to display and its WIDTH and HEIGHT.
520 See `doc-view-set-slice-using-mouse' for a more convenient way to
521 do that. To reset the slice use `doc-view-reset-slice'."
523 (let* ((size (image-size doc-view-current-image t))
524 (a (read-number (format "Top-left X (0..%d): " (car size))))
525 (b (read-number (format "Top-left Y (0..%d): " (cdr size))))
526 (c (read-number (format "Width (0..%d): " (- (car size) a))))
527 (d (read-number (format "Height (0..%d): " (- (cdr size) b)))))
529 (setq doc-view-current-slice (list x y width height))
531 (doc-view-goto-page doc-view-current-page))
533 (defun doc-view-set-slice-using-mouse ()
534 "Set the slice of the images that should be displayed.
535 You set the slice by pressing mouse-1 at its top-left corner and
536 dragging it to its bottom-right corner. See also
537 `doc-view-set-slice' and `doc-view-reset-slice'."
542 (concat "Press mouse-1 at the top-left corner and "
543 "drag it to the bottom-right corner!"))))
544 (when (eq (car e) 'drag-mouse-1)
545 (setq x (car (posn-object-x-y (event-start e))))
546 (setq y (cdr (posn-object-x-y (event-start e))))
547 (setq w (- (car (posn-object-x-y (event-end e))) x))
548 (setq h (- (cdr (posn-object-x-y (event-end e))) y))
550 (doc-view-set-slice x y w h)))
552 (defun doc-view-reset-slice ()
553 "Reset the current slice.
554 After calling this function the whole pages will be visible
557 (setq doc-view-current-slice nil)
559 (doc-view-goto-page doc-view-current-page))
563 (defun doc-view-insert-image (file &rest args)
564 "Insert the given png FILE.
565 ARGS is a list of image descriptors."
566 (let ((image (apply 'create-image file 'png nil args)))
567 (setq doc-view-current-image image)
568 (insert-image image (concat "[" file "]") nil doc-view-current-slice)))
570 (defun doc-view-sort (a b)
571 "Return non-nil if A should be sorted before B.
572 Predicate for sorting `doc-view-current-files'."
573 (if (< (length a) (length b))
575 (if (> (length a) (length b))
579 (defun doc-view-display-maybe (doc)
580 "Call `doc-view-display' iff we're in the image display."
581 (when (eq doc-view-current-display 'image)
582 (doc-view-display doc)))
584 (defun doc-view-display (doc)
585 "Start viewing the document DOC."
586 (set-buffer (get-file-buffer doc))
587 (setq doc-view-current-files
588 (sort (directory-files (doc-view-current-cache-dir) t
589 "page-[0-9]+\\.png" t)
591 (when (> (length doc-view-current-files) 0)
592 (doc-view-goto-page doc-view-current-page)))
594 (defun doc-view-buffer-message ()
595 (insert (propertize "Welcome to DocView!" 'face 'bold)
598 If you see this buffer it means that the document you want to
599 view gets converted to PNG now and the conversion of the first
600 page hasn't finished yet or
601 `doc-view-conversion-refresh-interval' is set to nil.
603 For now these keys are useful:
605 `q' : Bury this buffer. Conversion will go on in background.
606 `k' : Kill the conversion process and this buffer.\n")
607 (set-buffer-modified-p nil))
609 (defun doc-view-show-tooltip ()
611 (tooltip-show doc-view-current-info))
613 ;;;;; Toggle between text and image display
615 (defun doc-view-toggle-display ()
616 "Start or stop displaying a document file as a set of images.
617 This command toggles between showing the text of the document
618 file and showing the document as a set of images."
620 (if (get-text-property (point-min) 'display)
621 ;; Switch to text display
622 (let ((inhibit-read-only t))
624 (insert-file-contents doc-view-current-doc)
625 (use-local-map doc-view-mode-text-map)
626 (setq mode-name "DocView[text]"
627 doc-view-current-display 'text)
628 (if (called-interactively-p)
629 (message "Repeat this command to go back to displaying the file as images")))
630 ;; Switch to image display
631 (let ((inhibit-read-only t))
633 (doc-view-buffer-message)
634 (setq doc-view-current-page (or doc-view-current-page 1))
635 (if (file-exists-p (doc-view-current-cache-dir))
637 (message "DocView: using cached files!")
638 (doc-view-display doc-view-current-doc))
639 (doc-view-convert-current-doc))
640 (use-local-map doc-view-mode-map)
641 (setq mode-name (format "DocView")
642 doc-view-current-display 'image)
643 (if (called-interactively-p)
644 (message "Repeat this command to go back to displaying the file as text"))))
645 (set-buffer-modified-p nil))
647 ;;;;; Leave doc-view-mode and open the file for edit
649 (defun doc-view-edit-doc ()
650 "Leave `doc-view-mode' and open the current doc with an appropriate editing mode."
652 (let ((filename doc-view-current-doc)
653 (auto-mode-alist (append '(("\\.[eE]?[pP][sS]\\'" . ps-mode)
654 ("\\.\\(pdf\\|PDF\\|dvi\\|DVI\\)$" . fundamental-mode))
656 (kill-buffer (current-buffer))
657 (find-file filename)))
661 (defun doc-view-search-internal (regexp file)
662 "Return a list of FILE's pages that contain text matching REGEXP.
663 The value is an alist of the form (PAGE CONTEXTS) where PAGE is
664 the pagenumber and CONTEXTS are all lines of text containing a match."
666 (insert-file-contents file)
670 (while (re-search-forward (concat "\\(?:\\([
\f]\\)\\|\\("
671 regexp "\\)\\)") nil t)
672 (when (match-string 1) (incf page))
673 (when (match-string 2)
674 (if (/= page lastpage)
675 (setq matches (push (cons page
676 (list (buffer-substring
677 (line-beginning-position)
678 (line-end-position))))
683 ;; This page already is a match.
685 ;; This is the first match on page.
687 (list (buffer-substring
688 (line-beginning-position)
689 (line-end-position))))
691 (setq lastpage page)))
692 (nreverse matches))))
694 (defun doc-view-search-no-of-matches (list)
695 "Extract the number of matches from the search result LIST."
698 (setq no (+ no (1- (length p)))))
701 (defun doc-view-search ()
702 "Query for a regexp and search the current document.
703 If the current document hasn't been transformed to plain text
704 till now do that first. You should try searching anew when the
705 conversion finished."
707 ;; New search, so forget the old results.
708 (setq doc-view-current-search-matches nil)
709 (let ((txt (concat (doc-view-current-cache-dir)
711 (if (file-readable-p txt)
713 (setq doc-view-current-search-matches
714 (doc-view-search-internal
715 (read-from-minibuffer "Regexp: ")
717 (message "DocView: search yielded %d matches."
718 (doc-view-search-no-of-matches
719 doc-view-current-search-matches)))
720 ;; We must convert to TXT first!
721 (if doc-view-current-converter-process
722 (message "DocView: please wait till conversion finished.")
723 (let ((ext (file-name-extension doc-view-current-doc)))
726 ;; Doc is a PDF, so convert it to TXT
727 (doc-view-pdf->txt doc-view-current-doc txt))
729 ;; Doc is a PS, so convert it to PDF (which will be converted to
731 (doc-view-ps->pdf doc-view-current-doc
732 (concat (doc-view-current-cache-dir)
735 ;; Doc is a DVI. This means that a doc.pdf already exists in its
736 ;; cache subdirectory.
737 (doc-view-pdf->txt (concat (doc-view-current-cache-dir)
740 (t (error "DocView doesn't know what to do"))))))))
742 (defun doc-view-search-next-match (arg)
743 "Go to the ARGth next matching page."
745 (let* ((next-pages (remove-if (lambda (i) (<= (car i) doc-view-current-page))
746 doc-view-current-search-matches))
747 (page (car (nth (1- arg) next-pages))))
749 (doc-view-goto-page page)
751 doc-view-current-search-matches
752 (y-or-n-p "No more matches after current page. Wrap to first match? "))
753 (doc-view-goto-page (caar doc-view-current-search-matches))))))
755 (defun doc-view-search-previous-match (arg)
756 "Go to the ARGth previous matching page."
758 (let* ((prev-pages (remove-if (lambda (i) (>= (car i) doc-view-current-page))
759 doc-view-current-search-matches))
760 (page (car (nth (1- arg) (nreverse prev-pages)))))
762 (doc-view-goto-page page)
764 doc-view-current-search-matches
765 (y-or-n-p "No more matches before current page. Wrap to last match? "))
766 (doc-view-goto-page (caar (last doc-view-current-search-matches)))))))
768 ;;;; User interface commands and the mode
770 (put 'doc-view-mode 'mode-class 'special)
773 (define-derived-mode doc-view-mode nil "DocView"
774 "Major mode in DocView buffers.
775 You can use \\<doc-view-mode-map>\\[doc-view-toggle-display] to
776 toggle between display as a set of images and display as text."
778 (make-local-variable 'doc-view-current-files)
779 (make-local-variable 'doc-view-current-doc)
780 (make-local-variable 'doc-view-current-image)
781 (make-local-variable 'doc-view-current-page)
782 (make-local-variable 'doc-view-current-converter-process)
783 (make-local-variable 'doc-view-current-timer)
784 (make-local-variable 'doc-view-current-slice)
785 (make-local-variable 'doc-view-current-cache-dir)
786 (make-local-variable 'doc-view-current-info)
787 (make-local-variable 'doc-view-current-search-matches)
788 (setq doc-view-current-doc (buffer-file-name))
789 (insert-file-contents doc-view-current-doc)
790 (use-local-map doc-view-mode-text-map)
791 (setq mode-name "DocView[text]"
792 doc-view-current-display 'text
794 revert-buffer-function 'doc-view-reconvert-doc)
795 ;; Switch to image display if possible
796 (if (and (display-images-p)
797 (image-type-available-p 'png)
798 (not (get-text-property (point-min) 'display)))
799 (doc-view-toggle-display))
802 (substitute-command-keys
803 "Type \\[doc-view-toggle-display] to toggle between image and text display.")))
805 (defun doc-view-clear-cache ()
806 "Delete the whole cache (`doc-view-cache-directory')."
808 (dired-delete-file doc-view-cache-directory 'always)
809 (make-directory doc-view-cache-directory))
811 (defun doc-view-dired-cache ()
812 "Open `dired' in `doc-view-cache-directory'."
814 (dired doc-view-cache-directory))
819 ;; mode: outline-minor
822 ;; arch-tag: 5d6e5c5e-095f-489e-b4e4-1ca90a7d79be
823 ;;; doc-view.el ends here