(line-move-1): If we did not move as far as desired, ensure that
[bpt/emacs.git] / lisp / doc-view.el
index 57a07eb..3cf6d8c 100644 (file)
@@ -8,10 +8,10 @@
 
 ;; This file is part of GNU Emacs.
 
-;; GNU Emacs is free software; you can redistribute it and/or modify
+;; GNU Emacs is free software: you can redistribute it and/or modify
 ;; it under the terms of the GNU General Public License as published by
-;; the Free Software Foundation; either version 3, or (at your option)
-;; any later version.
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
 
 ;; GNU Emacs is distributed in the hope that it will be useful,
 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
 ;; GNU General Public License for more details.
 
 ;; You should have received a copy of the GNU General Public License
-;; along with GNU Emacs; see the file COPYING.  If not, write to the
-;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
-;; Boston, MA 02110-1301, USA.
+;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
 
 ;;; Requirements:
 
 ;; doc-view.el requires GNU Emacs 22.1 or newer.  You also need Ghostscript,
-;; `dvipdfm' which comes with teTeX and `pdftotext', which comes with xpdf
-;; (http://www.foolabs.com/xpdf/) or poppler (http://poppler.freedesktop.org/).
+;; `dvipdf' (comes with Ghostscript) or `dvipdfm' (comes with teTeX or TeXLive)
+;; and `pdftotext', which comes with xpdf (http://www.foolabs.com/xpdf/) or
+;; poppler (http://poppler.freedesktop.org/).
 
 ;;; Commentary:
 
 ;; - share more code with image-mode.
 ;; - better menu.
 ;; - Bind slicing to a drag event.
-;; - doc-view-fit-doc-to-window and doc-view-fit-window-to-doc.
+;; - doc-view-fit-doc-to-window and doc-view-fit-window-to-doc?
 ;; - zoom the region around the cursor (like xdvi).
 ;; - get rid of the silly arrow in the fringe.
 ;; - improve anti-aliasing (pdf-utils gets it better).
 
 ;;; Code:
 
+(eval-when-compile (require 'cl))
 (require 'dired)
 (require 'image-mode)
 (require 'jka-compr)
@@ -171,7 +171,21 @@ Higher values result in larger images."
   "Program to convert DVI files to PDF.
 
 DVI file will be converted to PDF before the resulting PDF is
-converted to PNG."
+converted to PNG.
+
+If this and `doc-view-dvipdf-program' are set,
+`doc-view-dvipdf-program' will be preferred."
+  :type 'file
+  :group 'doc-view)
+
+(defcustom doc-view-dvipdf-program (executable-find "dvipdf")
+  "Program to convert DVI files to PDF.
+
+DVI file will be converted to PDF before the resulting PDF is
+converted to PNG.
+
+If this and `doc-view-dvipdfm-program' are set,
+`doc-view-dvipdf-program' will be preferred."
   :type 'file
   :group 'doc-view)
 
@@ -214,6 +228,7 @@ has finished."
   (let ((ol (image-mode-window-get 'overlay winprops)))
     (if ol
         (setq ol (copy-overlay ol))
+      (assert (not (get-char-property (point-min) 'display)))
       (setq ol (make-overlay (point-min) (point-max) nil t))
       (overlay-put ol 'doc-view t))
     (overlay-put ol 'window (car winprops))
@@ -221,18 +236,23 @@ has finished."
 
 (defvar doc-view-current-files nil
   "Only used internally.")
+(make-variable-buffer-local 'doc-view-current-files)
 
-(defvar doc-view-current-converter-process nil
+(defvar doc-view-current-converter-processes nil
   "Only used internally.")
+(make-variable-buffer-local 'doc-view-current-converter-processes)
 
 (defvar doc-view-current-timer nil
   "Only used internally.")
+(make-variable-buffer-local 'doc-view-current-timer)
 
 (defvar doc-view-current-cache-dir nil
   "Only used internally.")
+(make-variable-buffer-local 'doc-view-current-cache-dir)
 
 (defvar doc-view-current-search-matches nil
   "Only used internally.")
+(make-variable-buffer-local 'doc-view-current-search-matches)
 
 (defvar doc-view-pending-cache-flush nil
   "Only used internally.")
@@ -248,11 +268,15 @@ files inside an archive it is a temporary copy of
 the (uncompressed, extracted) file residing in
 `doc-view-cache-directory'.")
 
+(defvar doc-view-doc-type nil
+  "The type of document in the current buffer.
+Can be `dvi', `pdf', or `ps'.")
+
 ;;;; DocView Keymaps
 
 (defvar doc-view-mode-map
   (let ((map (make-sparse-keymap)))
-    (suppress-keymap map)
+    (set-keymap-parent map image-mode-map)
     ;; Navigation in the document
     (define-key map (kbd "n")         'doc-view-next-page)
     (define-key map (kbd "p")         'doc-view-previous-page)
@@ -265,13 +289,10 @@ the (uncompressed, extracted) file residing in
     (define-key map (kbd "M-<")       'doc-view-first-page)
     (define-key map (kbd "M->")       'doc-view-last-page)
     (define-key map [remap goto-line] 'doc-view-goto-page)
-    (define-key map [remap scroll-up] 'image-scroll-up)
-    (define-key map [remap scroll-down] 'image-scroll-down)
     ;; Zoom in/out.
     (define-key map "+"               'doc-view-enlarge)
     (define-key map "-"               'doc-view-shrink)
-    ;; Killing/burying the buffer (and the process)
-    (define-key map (kbd "q")         'bury-buffer)
+    ;; Killing the buffer (and the process)
     (define-key map (kbd "k")         'doc-view-kill-proc-and-buffer)
     (define-key map (kbd "K")         'doc-view-kill-proc)
     ;; Slicing the image
@@ -282,11 +303,6 @@ the (uncompressed, extracted) file residing in
     (define-key map (kbd "C-s")       'doc-view-search)
     (define-key map (kbd "<find>")    'doc-view-search)
     (define-key map (kbd "C-r")       'doc-view-search-backward)
-    ;; Scrolling
-    (define-key map [remap forward-char]  'image-forward-hscroll)
-    (define-key map [remap backward-char] 'image-backward-hscroll)
-    (define-key map [remap next-line]     'image-next-line)
-    (define-key map [remap previous-line] 'image-previous-line)
     ;; Show the tooltip
     (define-key map (kbd "C-t")       'doc-view-show-tooltip)
     ;; Toggle between text and image display or editing
@@ -336,7 +352,7 @@ the (uncompressed, extracted) file residing in
       (when (and (> page len)
                  ;; As long as the converter is running, we don't know
                  ;; how many pages will be available.
-                 (null doc-view-current-converter-process))
+                 (null doc-view-current-converter-processes))
        (setq page len)))
     (setf (doc-view-current-page) page
          (doc-view-current-info)
@@ -344,7 +360,7 @@ the (uncompressed, extracted) file residing in
           (propertize
            (format "Page %d of %d." page len) 'face 'bold)
           ;; Tell user if converting isn't finished yet
-          (if doc-view-current-converter-process
+          (if doc-view-current-converter-processes
               " (still converting...)\n"
             "\n")
           ;; Display context infos if this page matches the last search
@@ -360,9 +376,22 @@ the (uncompressed, extracted) file residing in
     ;; We used to find the file name from doc-view-current-files but
     ;; that's not right if the pages are not generated sequentially
     ;; or if the page isn't in doc-view-current-files yet.
-    (doc-view-insert-image (expand-file-name (format "page-%d.png" page)
-                                             (doc-view-current-cache-dir))
-                           :pointer 'arrow)
+    (let ((file (expand-file-name (format "page-%d.png" page)
+                                  (doc-view-current-cache-dir))))
+      (doc-view-insert-image file :pointer 'arrow)
+      (when (and (not (file-exists-p file))
+                 doc-view-current-converter-processes)
+        ;; The PNG file hasn't been generated yet.
+        (doc-view-pdf->png-1 doc-view-buffer-file-name file page
+                             (lexical-let ((page page)
+                                           (win (selected-window)))
+                               (lambda ()
+                                 (and (eq (current-buffer) (window-buffer win))
+                                      ;; If we changed page in the mean
+                                      ;; time, don't mess things up.
+                                      (eq (doc-view-current-page win) page)
+                                      (with-selected-window win
+                                        (doc-view-goto-page page))))))))
     (overlay-put (doc-view-current-overlay)
                  'help-echo (doc-view-current-info))))
 
@@ -393,7 +422,7 @@ the (uncompressed, extracted) file residing in
     (let ((cur-page (doc-view-current-page)))
       (doc-view-next-page)
       (when (/= cur-page (doc-view-current-page))
-       (set-window-vscroll nil 0)))))
+       (image-scroll-down nil)))))
 
 (defun doc-view-scroll-down-or-previous-page ()
   "Scroll page down if possible, else goto previous page."
@@ -407,12 +436,11 @@ the (uncompressed, extracted) file residing in
 ;;;; Utility Functions
 
 (defun doc-view-kill-proc ()
-  "Kill the current converter process."
+  "Kill the current converter process(es)."
   (interactive)
-  (when doc-view-current-converter-process
+  (while doc-view-current-converter-processes
     (ignore-errors ;; Maybe it's dead already?
-      (kill-process doc-view-current-converter-process))
-    (setq doc-view-current-converter-process nil))
+      (kill-process (pop doc-view-current-converter-processes))))
   (when doc-view-current-timer
     (cancel-timer doc-view-current-timer)
     (setq doc-view-current-timer nil))
@@ -465,6 +493,7 @@ It's a subdirectory of `doc-view-cache-directory'."
                    "-"
                    (let ((file doc-view-buffer-file-name))
                      (with-temp-buffer
+                       (set-buffer-multibyte nil)
                        (insert-file-contents-literally file)
                        (md5 (current-buffer)))))
             doc-view-cache-directory)))))
@@ -485,8 +514,10 @@ Image types are symbols like `dvi', `postscript' or `pdf'."
        (cond
        ((eq type 'dvi)
         (and (doc-view-mode-p 'pdf)
-             doc-view-dvipdfm-program
-             (executable-find doc-view-dvipdfm-program)))
+             (or (and doc-view-dvipdf-program
+                      (executable-find doc-view-dvipdf-program))
+                 (and doc-view-dvipdfm-program
+                      (executable-find doc-view-dvipdfm-program)))))
        ((or (eq type 'postscript) (eq type 'ps) (eq type 'eps)
             (eq type 'pdf))
         (and doc-view-ghostscript-program
@@ -520,155 +551,150 @@ Should be invoked when the cached images aren't up-to-date."
     (dired-delete-file (doc-view-current-cache-dir) 'always))
   (doc-view-initiate-display))
 
-(defun doc-view-dvi->pdf-sentinel (proc event)
-  "If DVI->PDF conversion was successful, convert the PDF to PNG now."
-  (if (not (string-match "finished" event))
-      (message "DocView: dvi->pdf process changed status to %s." event)
-    (with-current-buffer (process-get proc 'buffer)
-      (let ((callback (process-get proc 'callback))
-           (cb-args  (process-get proc 'cb-args)))
-       (setq doc-view-current-converter-process nil
-             mode-line-process nil)
-       (apply callback cb-args)))))
-
-(defun doc-view-dvi->pdf (dvi pdf callback &rest cb-args)
-  "Convert DVI to PDF asynchronously and call CALLBACK with CB-ARGS when finished."
-  (setq doc-view-current-converter-process
-       (start-process "dvi->pdf" doc-view-conversion-buffer
-                      doc-view-dvipdfm-program
-                      "-o" pdf dvi)
-       mode-line-process (list (format ":%s" doc-view-current-converter-process)))
-  (set-process-sentinel doc-view-current-converter-process
-                       'doc-view-dvi->pdf-sentinel)
-  (process-put doc-view-current-converter-process 'buffer   (current-buffer))
-  (process-put doc-view-current-converter-process 'callback callback)
-  (process-put doc-view-current-converter-process 'cb-args  cb-args))
-
-(defun doc-view-pdf/ps->png-sentinel (proc event)
-  "If PDF/PS->PNG conversion was successful, update the display."
+(defun doc-view-sentinel (proc event)
+  "Generic sentinel for doc-view conversion processes."
   (if (not (string-match "finished" event))
-      (message "DocView: converter process changed status to %s." event)
-    ;; FIXME: kill the process if we kill the buffer?
+      (message "DocView: process %s changed status to %s."
+               (process-name proc) event)
     (when (buffer-live-p (process-get proc 'buffer))
       (with-current-buffer (process-get proc 'buffer)
-        (setq doc-view-current-converter-process nil
-              mode-line-process nil)
-        (when doc-view-current-timer
-          (cancel-timer doc-view-current-timer)
-          (setq doc-view-current-timer nil))
-        ;; Yippie, finished.  Update the display!
-        (doc-view-display (current-buffer) 'force)))))
+        (setq doc-view-current-converter-processes
+              (delq proc doc-view-current-converter-processes))
+        (setq mode-line-process
+              (if doc-view-current-converter-processes
+                  (format ":%s" (car doc-view-current-converter-processes))))
+        (funcall (process-get proc 'callback))))))
+
+(defun doc-view-start-process (name program args callback)
+  ;; Make sure the process is started in an existing directory, (rather than
+  ;; some file-name-handler-managed dir, for example).
+  (let* ((default-directory (if (file-readable-p default-directory)
+                               default-directory
+                             (expand-file-name "~/")))
+         (proc (apply 'start-process name doc-view-conversion-buffer
+                      program args)))
+    (push proc doc-view-current-converter-processes)
+    (setq mode-line-process (list (format ":%s" proc)))
+    (set-process-sentinel proc 'doc-view-sentinel)
+    (process-put proc 'buffer   (current-buffer))
+    (process-put proc 'callback callback)))
+
+(defun doc-view-dvi->pdf (dvi pdf callback)
+  "Convert DVI to PDF asynchronously and call CALLBACK when finished."
+  ;; Prefer dvipdf over dvipdfm, because the latter has problems if the DVI
+  ;; references and includes other PS files.
+  (if (and doc-view-dvipdf-program
+          (executable-find doc-view-dvipdf-program))
+      (doc-view-start-process "dvi->pdf" doc-view-dvipdf-program
+                           (list dvi pdf)
+                           callback)
+    (doc-view-start-process "dvi->pdf" doc-view-dvipdfm-program
+                           (list "-o" pdf dvi)
+                           callback)))
+
 
 (defun doc-view-pdf/ps->png (pdf-ps png)
   "Convert PDF-PS to PNG asynchronously."
-  (setq doc-view-current-converter-process
-        ;; Make sure the process is started in an existing directory,
-        ;; (rather than some file-name-handler-managed dir, for example).
-        (let ((default-directory (file-name-directory pdf-ps)))
-          (apply 'start-process
-                 (append (list "pdf/ps->png" doc-view-conversion-buffer
-                               doc-view-ghostscript-program)
-                         doc-view-ghostscript-options
-                         (list (format "-r%d" (round doc-view-resolution)))
-                         (list (concat "-sOutputFile=" png))
-                         (list pdf-ps))))
-       mode-line-process (list (format ":%s" doc-view-current-converter-process)))
-  (process-put doc-view-current-converter-process
-              'buffer (current-buffer))
-  (set-process-sentinel doc-view-current-converter-process
-                       'doc-view-pdf/ps->png-sentinel)
+  (doc-view-start-process
+   "pdf/ps->png" doc-view-ghostscript-program
+   (append doc-view-ghostscript-options
+           (list (format "-r%d" (round doc-view-resolution))
+                 (concat "-sOutputFile=" png)
+                 pdf-ps))
+   (lambda ()
+     (when doc-view-current-timer
+       (cancel-timer doc-view-current-timer)
+       (setq doc-view-current-timer nil))
+     (doc-view-display (current-buffer) 'force)))
+  ;; Update the displayed pages as soon as they're done generating.
   (when doc-view-conversion-refresh-interval
     (setq doc-view-current-timer
-         (run-at-time "1 secs" doc-view-conversion-refresh-interval
-                      'doc-view-display
-                      (current-buffer)))))
-
-(defun doc-view-pdf->txt-sentinel (proc event)
-  (if (not (string-match "finished" event))
-      (message "DocView: converter process changed status to %s." event)
-    (let ((current-buffer (current-buffer))
-         (proc-buffer    (process-get proc 'buffer)))
-      (with-current-buffer proc-buffer
-        (let ((callback (process-get doc-view-current-converter-process 'callback))
-             (cb-args  (process-get doc-view-current-converter-process 'cb-args)))
-         (setq doc-view-current-converter-process nil
-               mode-line-process nil)
-         ;; If the user looks at the DocView buffer where the conversion was
-         ;; performed, call callback.
-         (when (eq current-buffer proc-buffer)
-           (apply callback cb-args)))))))
-
-(defun doc-view-pdf->txt (pdf txt callback &rest cb-args)
-  "Convert PDF to TXT asynchronously and call CALLBACK with CB-ARGS when finished."
-  (setq doc-view-current-converter-process
-       (start-process "pdf->txt" doc-view-conversion-buffer
-                      doc-view-pdftotext-program "-raw"
-                      pdf txt)
-       mode-line-process (list (format ":%s" doc-view-current-converter-process)))
-  (set-process-sentinel doc-view-current-converter-process
-                       'doc-view-pdf->txt-sentinel)
-  (process-put doc-view-current-converter-process 'buffer (current-buffer))
-  (process-put doc-view-current-converter-process 'callback callback)
-  (process-put doc-view-current-converter-process 'cb-args cb-args))
-
-(defun doc-view-doc->txt (callback &rest cb-args)
-  "Convert the current document to text and call CALLBACK with CB-ARGS when done."
-  (make-directory (doc-view-current-cache-dir))
-  (let ((ext (file-name-extension doc-view-buffer-file-name)))
-    (cond
-     ((string= ext "pdf")
-      ;; Doc is a PDF, so convert it to TXT
-      (if cb-args
-         (doc-view-pdf->txt doc-view-buffer-file-name txt callback cb-args)
-       (doc-view-pdf->txt doc-view-buffer-file-name txt callback)))
-     ((string= ext "ps")
-      ;; Doc is a PS, so convert it to PDF (which will be converted to
-      ;; TXT thereafter).
-      (let ((pdf (expand-file-name "doc.pdf"
-                                  (doc-view-current-cache-dir))))
-       (if cb-args
-           (doc-view-ps->pdf doc-view-buffer-file-name pdf
-                             'doc-view-pdf->txt
-                             pdf txt callback cb-args)
-         (doc-view-ps->pdf doc-view-buffer-file-name pdf
-                           'doc-view-pdf->txt
-                           pdf txt callback))))
-     ((string= ext "dvi")
-      ;; Doc is a DVI.  This means that a doc.pdf already exists in its
-      ;; cache subdirectory.
-      (if cb-args
-         (doc-view-pdf->txt (expand-file-name "doc.pdf"
-                                              (doc-view-current-cache-dir))
-                            txt callback cb-args)
-       (doc-view-pdf->txt (expand-file-name "doc.pdf"
-                                            (doc-view-current-cache-dir))
-                          txt callback)))
-     (t (error "DocView doesn't know what to do")))))
-
-(defun doc-view-ps->pdf-sentinel (proc event)
-  (if (not (string-match "finished" event))
-      (message "DocView: converter process changed status to %s." event)
-    (with-current-buffer (process-get proc 'buffer)
-      (setq doc-view-current-converter-process nil
-           mode-line-process nil)
-      (apply (process-get proc 'callback) (process-get proc 'cb-args)))))
-
-(defun doc-view-ps->pdf (ps pdf callback &rest cb-args)
-  "Convert PS to PDF asynchronously and call CALLBACK with CB-ARGS when finished."
-  (setq doc-view-current-converter-process
-       (start-process "ps->pdf" doc-view-conversion-buffer
-                      doc-view-ps2pdf-program
-                      ;; Avoid security problems when rendering files from
-                      ;; untrusted sources.
-                      "-dSAFER"
-                      ;; in-file and out-file
-                      ps pdf)
-       mode-line-process (list (format ":%s" doc-view-current-converter-process)))
-  (set-process-sentinel doc-view-current-converter-process
-                       'doc-view-ps->pdf-sentinel)
-  (process-put doc-view-current-converter-process 'buffer   (current-buffer))
-  (process-put doc-view-current-converter-process 'callback callback)
-  (process-put doc-view-current-converter-process 'cb-args  cb-args))
+          (run-at-time "1 secs" doc-view-conversion-refresh-interval
+                       'doc-view-display
+                       (current-buffer)))))
+
+(defun doc-view-pdf->png-1 (pdf png page callback)
+  "Convert a PAGE of a PDF file to PNG asynchronously.
+Call CALLBACK with no arguments when done."
+  (doc-view-start-process
+   "pdf->png-1" doc-view-ghostscript-program
+   (append doc-view-ghostscript-options
+           (list (format "-r%d" (round doc-view-resolution))
+                 ;; Sadly, `gs' only supports the page-range
+                 ;; for PDF files.
+                 (format "-dFirstPage=%d" page)
+                 (format "-dLastPage=%d" page)
+                 (concat "-sOutputFile=" png)
+                 pdf))
+   callback))
+
+(defun doc-view-pdf->png (pdf png pages)
+  "Convert a PDF file to PNG asynchronously.
+Start by converting PAGES, and then the rest."
+  (if (null pages)
+      (doc-view-pdf/ps->png pdf png)
+    ;; We could render several `pages' with a single process if they're
+    ;; (almost) consecutive, but since in 99% of the cases, there'll be only
+    ;; a single page anyway, and of the remaining 1%, few cases will have
+    ;; consecutive pages, it's not worth the trouble.
+    (lexical-let ((pdf pdf) (png png) (rest (cdr pages)))
+      (doc-view-pdf->png-1
+       pdf (format png (car pages)) (car pages)
+       (lambda ()
+         (if rest
+             (doc-view-pdf->png pdf png rest)
+           ;; Yippie, the important pages are done, update the display.
+           (clear-image-cache)
+           ;; Convert the rest of the pages.
+           (doc-view-pdf/ps->png pdf png)))))))
+
+(defun doc-view-pdf->txt (pdf txt callback)
+  "Convert PDF to TXT asynchronously and call CALLBACK when finished."
+  (doc-view-start-process "pdf->txt" doc-view-pdftotext-program
+                          (list "-raw" pdf txt)
+                          callback))
+
+(defun doc-view-doc->txt (txt callback)
+  "Convert the current document to text and call CALLBACK when done."
+  (make-directory (doc-view-current-cache-dir) t)
+  (case doc-view-doc-type
+    (pdf
+     ;; Doc is a PDF, so convert it to TXT
+     (doc-view-pdf->txt doc-view-buffer-file-name txt callback))
+    (ps
+     ;; Doc is a PS, so convert it to PDF (which will be converted to
+     ;; TXT thereafter).
+     (lexical-let ((pdf (expand-file-name "doc.pdf"
+                                          (doc-view-current-cache-dir)))
+                   (txt txt)
+                   (callback callback))
+       (doc-view-ps->pdf doc-view-buffer-file-name pdf
+                         (lambda () (doc-view-pdf->txt pdf txt callback)))))
+    (dvi
+     ;; Doc is a DVI.  This means that a doc.pdf already exists in its
+     ;; cache subdirectory.
+     (doc-view-pdf->txt (expand-file-name "doc.pdf"
+                                          (doc-view-current-cache-dir))
+                        txt callback))
+    (t (error "DocView doesn't know what to do"))))
+
+(defun doc-view-ps->pdf (ps pdf callback)
+  "Convert PS to PDF asynchronously and call CALLBACK when finished."
+  (doc-view-start-process "ps->pdf" doc-view-ps2pdf-program
+                          (list
+                           ;; Avoid security problems when rendering files from
+                           ;; untrusted sources.
+                           "-dSAFER"
+                           ;; in-file and out-file
+                           ps pdf)
+                          callback))
+
+(defun doc-view-active-pages ()
+  (let ((pages ()))
+    (dolist (win (get-buffer-window-list (current-buffer) nil 'visible))
+      (let ((page (image-mode-window-get 'page win)))
+        (unless (memq page pages) (push page pages))))
+    pages))
 
 (defun doc-view-convert-current-doc ()
   "Convert `doc-view-buffer-file-name' to a set of png files, one file per page.
@@ -682,15 +708,23 @@ Those files are saved in the directory given by the function
   (setq doc-view-pending-cache-flush t)
   (let ((png-file (expand-file-name "page-%d.png"
                                     (doc-view-current-cache-dir))))
-    (make-directory (doc-view-current-cache-dir))
-    (if (not (string= (file-name-extension doc-view-buffer-file-name) "dvi"))
-       ;; Convert to PNG images.
-       (doc-view-pdf/ps->png doc-view-buffer-file-name png-file)
-      ;; DVI files have to be converted to PDF before Ghostscript can process
-      ;; it.
-      (let ((pdf (expand-file-name "doc.pdf" doc-view-current-cache-dir)))
-       (doc-view-dvi->pdf doc-view-buffer-file-name
-                          pdf 'doc-view-pdf/ps->png pdf png-file)))))
+    (make-directory (doc-view-current-cache-dir) t)
+    (case doc-view-doc-type
+      (dvi
+       ;; DVI files have to be converted to PDF before Ghostscript can process
+       ;; it.
+       (lexical-let
+           ((pdf (expand-file-name "doc.pdf" doc-view-current-cache-dir))
+            (png-file png-file))
+         (doc-view-dvi->pdf doc-view-buffer-file-name pdf
+                            (lambda () (doc-view-pdf/ps->png pdf png-file)))))
+     (pdf
+      (let ((pages (doc-view-active-pages)))
+        ;; Convert PDF to PNG images starting with the active pages.
+        (doc-view-pdf->png doc-view-buffer-file-name png-file pages)))
+      (t
+       ;; Convert to PNG images.
+       (doc-view-pdf/ps->png doc-view-buffer-file-name png-file)))))
 
 ;;;; Slicing
 
@@ -761,7 +795,7 @@ ARGS is a list of image descriptors."
                        (list (cons 'slice slice) image)
                      image))
                   ;; We're trying to display a page that doesn't exist.
-                  (doc-view-current-converter-process
+                  (doc-view-current-converter-processes
                    ;; Maybe the page doesn't exist *yet*.
                    "Cannot display this page (yet)!")
                   (t
@@ -799,16 +833,17 @@ have the page we want to view."
             (sort (directory-files (doc-view-current-cache-dir) t
                                    "page-[0-9]+\\.png" t)
                   'doc-view-sort))
-      (dolist (win (get-buffer-window-list buffer nil t))
-        (let* ((page (doc-view-current-page win))
-               (pagefile (expand-file-name (format "page-%d.png" page)
-                                             (doc-view-current-cache-dir))))
-          (when (or force
-                    (and (not (member pagefile prev-pages))
-                         (member pagefile doc-view-current-files)))
-            (with-selected-window win
-              (assert (eq (current-buffer) buffer))
-              (doc-view-goto-page page))))))))
+      (dolist (win (or (get-buffer-window-list buffer nil t)
+                      (list (selected-window))))
+       (let* ((page (doc-view-current-page win))
+              (pagefile (expand-file-name (format "page-%d.png" page)
+                                          (doc-view-current-cache-dir))))
+         (when (or force
+                   (and (not (member pagefile prev-pages))
+                        (member pagefile doc-view-current-files)))
+           (with-selected-window win
+             (assert (eq (current-buffer) buffer))
+             (doc-view-goto-page page))))))))
 
 (defun doc-view-buffer-message ()
   ;; Only show this message initially, not when refreshing the buffer (in which
@@ -836,12 +871,12 @@ For now these keys are useful:
 (defun doc-view-open-text ()
   "Open a buffer with the current doc's contents as text."
   (interactive)
-  (if doc-view-current-converter-process
+  (if doc-view-current-converter-processes
       (message "DocView: please wait till conversion finished.")
     (let ((txt (expand-file-name "doc.txt" (doc-view-current-cache-dir))))
       (if (file-readable-p txt)
          (find-file txt)
-       (doc-view-doc->txt 'doc-view-open-text)))))
+       (doc-view-doc->txt txt 'doc-view-open-text)))))
 
 ;;;;; Toggle between editing and viewing
 
@@ -942,9 +977,9 @@ If BACKWARD is non-nil, jump to the previous match."
                     (doc-view-search-no-of-matches
                      doc-view-current-search-matches)))
        ;; We must convert to TXT first!
-       (if doc-view-current-converter-process
+       (if doc-view-current-converter-processes
            (message "DocView: please wait till conversion finished.")
-         (doc-view-doc->txt 'doc-view-search nil))))))
+         (doc-view-doc->txt txt (lambda () (doc-view-search nil))))))))
 
 (defun doc-view-search-next-match (arg)
   "Go to the ARGth next matching page."
@@ -981,11 +1016,11 @@ If BACKWARD is non-nil, jump to the previous match."
 (defun doc-view-already-converted-p ()
   "Return non-nil if the current doc was already converted."
   (and (file-exists-p (doc-view-current-cache-dir))
-       (> 0 (length (directory-files (doc-view-current-cache-dir) nil "\\.png$")))))
+       (> (length (directory-files (doc-view-current-cache-dir) nil "\\.png$")) 0)))
 
 (defun doc-view-initiate-display ()
   ;; Switch to image display if possible
-  (if (doc-view-mode-p (intern (file-name-extension doc-view-buffer-file-name)))
+  (if (doc-view-mode-p doc-view-doc-type)
       (progn
        (doc-view-buffer-message)
        (setf (doc-view-current-page) (or (doc-view-current-page) 1))
@@ -1023,6 +1058,11 @@ If BACKWARD is non-nil, jump to the previous match."
   (remove-overlays (point-min) (point-max) 'doc-view t)
   (if (consp image-mode-winprops-alist) (setq image-mode-winprops-alist nil)))
 
+(defun doc-view-intersection (l1 l2)
+  (let ((l ()))
+    (dolist (x l1) (if (memq x l2) (push x l)))
+    l))
+
 ;;;###autoload
 (defun doc-view-mode ()
   "Major mode in DocView buffers.
@@ -1037,6 +1077,30 @@ toggle between displaying the document or editing it as text.
     (kill-all-local-variables)
     (set (make-local-variable 'doc-view-previous-major-mode) prev-major-mode))
 
+  ;; Figure out the document type.
+  (let ((name-types
+         (when buffer-file-name
+           (cdr (assoc (file-name-extension buffer-file-name)
+                       '(("dvi" dvi)
+                         ("pdf" pdf)
+                         ("epdf" pdf)
+                         ("ps" ps)
+                         ("eps" ps))))))
+        (content-types
+         (save-excursion
+           (goto-char (point-min))
+           (cond
+            ((looking-at "%!") '(ps))
+            ((looking-at "%PDF") '(pdf))
+            ((looking-at "\367\002") '(dvi))))))
+    (set (make-local-variable 'doc-view-doc-type)
+         (car (or (doc-view-intersection name-types content-types)
+                  (when (and name-types content-types)
+                    (error "Conflicting types: name says %s but content says %s"
+                           name-types content-types))
+                  name-types content-types
+                  (error "Cannot determine the document type")))))
+
   (doc-view-make-safe-dir doc-view-cache-directory)
   ;; Handle compressed files, remote files, files inside archives
   (set (make-local-variable 'doc-view-buffer-file-name)
@@ -1059,15 +1123,13 @@ toggle between displaying the document or editing it as text.
   (when (not (string= doc-view-buffer-file-name buffer-file-name))
     (write-region nil nil doc-view-buffer-file-name))
 
-  (make-local-variable 'doc-view-current-files)
-  (make-local-variable 'doc-view-current-converter-process)
-  (make-local-variable 'doc-view-current-timer)
-  (make-local-variable 'doc-view-current-cache-dir)
-  (make-local-variable 'doc-view-current-search-matches)
   (add-hook 'change-major-mode-hook
-           (lambda () (remove-overlays (point-min) (point-max) 'doc-view t))
+           (lambda ()
+              (doc-view-kill-proc)
+              (remove-overlays (point-min) (point-max) 'doc-view t))
            nil t)
   (add-hook 'clone-indirect-buffer-hook 'doc-view-clone-buffer-hook nil t)
+  (add-hook 'kill-buffer 'doc-view-kill-proc nil t)
 
   (remove-overlays (point-min) (point-max) 'doc-view t) ;Just in case.
   ;; Keep track of display info ([vh]scroll, page number, overlay, ...)