(Vtruncate_partial_width_windows): Improve docstring.
[bpt/emacs.git] / lisp / doc-view.el
index 2bf8b28..3ea4fce 100644 (file)
@@ -1,6 +1,6 @@
 ;;; doc-view.el --- View PDF/PostScript/DVI files in Emacs
 
-;; Copyright (C) 2007, 2008 Free Software Foundation, Inc.
+;; Copyright (C) 2007, 2008, 2009 Free Software Foundation, Inc.
 ;;
 ;; Author: Tassilo Horn <tassilo@member.fsf.org>
 ;; Maintainer: Tassilo Horn <tassilo@member.fsf.org>
@@ -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:
 
@@ -63,7 +62,7 @@
 ;; slice you can use `doc-view-set-slice' (bound to `s s') which will query you
 ;; for the coordinates of the slice's top-left corner and its width and height.
 ;; A much more convenient way to do the same is offered by the command
-;; `doc-view-set-slice-using-mouse' (bound to `s m').  After invokation you
+;; `doc-view-set-slice-using-mouse' (bound to `s m').  After invocation you
 ;; only have to press mouse-1 at the top-left corner and drag it to the
 ;; bottom-right corner of the desired slice.  To reset the slice use
 ;; `doc-view-reset-slice' (bound to `s r').
@@ -75,7 +74,7 @@
 ;; `C-r' you can do the same, but backwards.  To search for a new regexp give a
 ;; prefix arg to one of the search functions, e.g. by typing `C-u C-s'.  The
 ;; searching works by using a plain text representation of the document.  If
-;; that doesn't already exist the first invokation of `doc-view-search' (or
+;; that doesn't already exist the first invocation of `doc-view-search' (or
 ;; `doc-view-search-backward') starts the conversion.  When that finishes and
 ;; you're still viewing the document (i.e. you didn't switch to another buffer)
 ;; you're queried for the regexp then.
 
 ;;; Todo:
 
-;; - fix behavior with clone-indirect-buffer
-;; - allow different windows showing the same buffer to change pages
-;;   independently.
-;; - share more code with image-mode again.
+;; - add print command.
+;; - 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)
@@ -173,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)
 
@@ -198,12 +210,10 @@ Needed for searching."
   :type 'directory
   :group 'doc-view)
 
-(defcustom doc-view-conversion-buffer "*doc-view conversion output*"
-  "The buffer where messages from the converter programs go to."
-  :type 'string
-  :group 'doc-view)
+(defvar doc-view-conversion-buffer " *doc-view conversion output*"
+  "The buffer where messages from the converter programs go to.")
 
-(defcustom doc-view-conversion-refresh-interval 3
+(defcustom doc-view-conversion-refresh-interval 1
   "Interval in seconds between refreshes of the DocView buffer while converting.
 After such a refresh newly converted pages will be available for
 viewing.  If set to nil there won't be any refreshes and the
@@ -214,39 +224,39 @@ has finished."
 
 ;;;; Internal Variables
 
-(defvar doc-view-current-files nil
-  "Only used internally.")
+(defun doc-view-new-window-function (winprops)
+  (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))
+    (image-mode-window-put 'overlay ol winprops)))
 
-(defvar doc-view-current-page nil
+(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.")
-
-(defvar doc-view-current-slice 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.")
-
-(defvar doc-view-current-image nil
-  "Only used internally.")
-
-(defvar doc-view-current-overlay nil
-  "Only used internally.")
+(make-variable-buffer-local 'doc-view-current-search-matches)
 
 (defvar doc-view-pending-cache-flush nil
   "Only used internally.")
 
-(defvar doc-view-current-info nil
-  "Only used internally.")
-
 (defvar doc-view-previous-major-mode nil
   "Only used internally.")
 
@@ -258,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)
@@ -275,13 +289,11 @@ 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)
+    (define-key map (kbd "RET")       'image-next-line)
     ;; 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
@@ -292,15 +304,12 @@ 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
     (define-key map (kbd "C-c C-c")   'doc-view-toggle-display)
+    ;; Open a new buffer with doc's text contents
+    (define-key map (kbd "C-c C-t")   'doc-view-open-text)
     ;; Reconvert the current document
     (define-key map (kbd "g")         'revert-buffer)
     (define-key map (kbd "r")         'revert-buffer)
@@ -328,49 +337,76 @@ the (uncompressed, extracted) file residing in
 
 ;;;; Navigation Commands
 
+(defmacro doc-view-current-page (&optional win)
+  `(image-mode-window-get 'page ,win))
+(defmacro doc-view-current-info () `(image-mode-window-get 'info))
+(defmacro doc-view-current-overlay () `(image-mode-window-get 'overlay))
+(defmacro doc-view-current-image () `(image-mode-window-get 'image))
+(defmacro doc-view-current-slice () `(image-mode-window-get 'slice))
+
 (defun doc-view-goto-page (page)
   "View the page given by PAGE."
   (interactive "nPage: ")
-  (let ((len (length doc-view-current-files)))
+  (let ((len (length doc-view-current-files))
+       (hscroll (window-hscroll)))
     (if (< page 1)
        (setq page 1)
-      (when (> page len)
+      (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-processes))
        (setq page len)))
-    (setq doc-view-current-page page
-         doc-view-current-info
+    (setf (doc-view-current-page) page
+         (doc-view-current-info)
          (concat
           (propertize
-           (format "Page %d of %d."
-                   doc-view-current-page
-                   len) 'face 'bold)
+           (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
           (when (and doc-view-current-search-matches
-                     (assq doc-view-current-page
-                           doc-view-current-search-matches))
+                     (assq page doc-view-current-search-matches))
             (concat (propertize "Search matches:\n" 'face 'bold)
                     (let ((contexts ""))
-                      (dolist (m (cdr (assq doc-view-current-page
+                      (dolist (m (cdr (assq page
                                             doc-view-current-search-matches)))
                         (setq contexts (concat contexts "  - \"" m "\"\n")))
                       contexts)))))
     ;; Update the buffer
-    (doc-view-insert-image (nth (1- page) doc-view-current-files)
-                           :pointer 'arrow)
-    (overlay-put doc-view-current-overlay 'help-echo doc-view-current-info)))
+    ;; 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.
+    (let ((file (expand-file-name (format "page-%d.png" page)
+                                  (doc-view-current-cache-dir))))
+      (doc-view-insert-image file :pointer 'arrow)
+      (set-window-hscroll (selected-window) hscroll)
+      (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))))
 
 (defun doc-view-next-page (&optional arg)
   "Browse ARG pages forward."
   (interactive "p")
-  (doc-view-goto-page (+ doc-view-current-page (or arg 1))))
+  (doc-view-goto-page (+ (doc-view-current-page) (or arg 1))))
 
 (defun doc-view-previous-page (&optional arg)
   "Browse ARG pages backward."
   (interactive "p")
-  (doc-view-goto-page (- doc-view-current-page (or arg 1))))
+  (doc-view-goto-page (- (doc-view-current-page) (or arg 1))))
 
 (defun doc-view-first-page ()
   "View the first page."
@@ -385,29 +421,35 @@ the (uncompressed, extracted) file residing in
 (defun doc-view-scroll-up-or-next-page ()
   "Scroll page up if possible, else goto next page."
   (interactive)
-  (when (= (window-vscroll) (image-scroll-up nil))
-    (let ((cur-page doc-view-current-page))
+  (let ((hscroll (window-hscroll))
+       (cur-page (doc-view-current-page)))
+    (when (= (window-vscroll) (image-scroll-up nil))
       (doc-view-next-page)
-      (when (/= cur-page doc-view-current-page)
-       (set-window-vscroll nil 0)))))
+      (when (/= cur-page (doc-view-current-page))
+       (image-bob)
+       (image-bol 1))
+      (set-window-hscroll (selected-window) hscroll))))
 
 (defun doc-view-scroll-down-or-previous-page ()
   "Scroll page down if possible, else goto previous page."
   (interactive)
-  (when (= (window-vscroll) (image-scroll-down nil))
-    (let ((cur-page doc-view-current-page))
+  (let ((hscroll (window-hscroll))
+       (cur-page (doc-view-current-page)))
+    (when (= (window-vscroll) (image-scroll-down nil))
       (doc-view-previous-page)
-      (when (/= cur-page doc-view-current-page)
-       (image-scroll-up nil)))))
+      (when (/= cur-page (doc-view-current-page))
+       (image-eob)
+       (image-bol 1))
+      (set-window-hscroll (selected-window) hscroll))))
 
 ;;;; 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
-    (kill-process doc-view-current-converter-process)
-    (setq doc-view-current-converter-process nil))
+  (while doc-view-current-converter-processes
+    (ignore-errors ;; Maybe it's dead already?
+      (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))
@@ -460,6 +502,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)))))
@@ -480,8 +523,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
@@ -515,118 +560,159 @@ 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."
+(defun doc-view-sentinel (proc event)
+  "Generic sentinel for doc-view conversion processes."
   (if (not (string-match "finished" event))
-      (message "DocView: dvi->pdf process changed status to %s." event)
-    (with-current-buffer (process-get proc 'buffer)
-      (setq doc-view-current-converter-process nil
-            mode-line-process nil)
-      ;; Now go on converting this PDF to a set of PNG files.
-      (let* ((pdf (process-get proc 'pdf-file))
-             (png (expand-file-name "page-%d.png"
-                                    (doc-view-current-cache-dir))))
-        (doc-view-pdf/ps->png pdf png)))))
-
-(defun doc-view-dvi->pdf (dvi pdf)
-  "Convert DVI to PDF asynchronously."
-  (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 'pdf-file pdf))
-
-(defun doc-view-pdf/ps->png-sentinel (proc event)
-  "If PDF/PS->PNG conversion was successful, update the display."
-  (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)
-      (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))))
+      (message "DocView: process %s changed status to %s."
+               (process-name proc)
+              (if (string-match "\\(.+\\)\n?\\'" event)
+                  (match-string 1 event)
+                event))
+    (when (buffer-live-p (process-get proc 'buffer))
+      (with-current-buffer (process-get proc 'buffer)
+        (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
-        (setq doc-view-current-converter-process nil
-              mode-line-process nil)
-        ;; If the user looks at the DocView buffer where the conversion was
-        ;; performed, search anew.  This time it will be queried for a regexp.
-        (when (eq current-buffer proc-buffer)
-          (doc-view-search nil))))))
-
-(defun doc-view-pdf->txt (pdf txt)
-  "Convert PDF to TXT asynchronously."
-  (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)))
-
-(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)
-      ;; Now we can transform to plain text.
-      (doc-view-pdf->txt (process-get proc 'pdf-file)
-                         (expand-file-name "doc.txt"
-                                           (doc-view-current-cache-dir))))))
-
-(defun doc-view-ps->pdf (ps pdf)
-  "Convert PS to PDF asynchronously."
-  (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 'pdf-file pdf))
+          (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))
+
+(declare-function clear-image-cache "image.c" (&optional filter))
+
+(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."
+  (or doc-view-pdftotext-program
+      (error "You need the `pdftotext' program to convert a PDF to text"))
+  (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."
+  (or doc-view-ps2pdf-program
+      (error "You need the `ps2pdf' program to convert PS to PDF"))
+  (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.
@@ -639,19 +725,39 @@ Those files are saved in the directory given by the function
   ;; resets during the redisplay).
   (setq doc-view-pending-cache-flush t)
   (let ((png-file (expand-file-name "page-%d.png"
+                                    (doc-view-current-cache-dir)))
+       (res-file (expand-file-name "resolution.el"
                                     (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.
-      (doc-view-dvi->pdf doc-view-buffer-file-name
-                        (expand-file-name "doc.pdf"
-                                           doc-view-current-cache-dir)))))
+    (make-directory (doc-view-current-cache-dir) t)
+    ;; Save the used resolution so that it can be restored when
+    ;; reading the cached files.
+    (let ((res doc-view-resolution))
+      (with-temp-buffer
+       (princ res (current-buffer))
+        ;; Don't use write-file, so as to avoid prompts for `require-newline',
+        ;; or for pre-existing buffers with the same name, ...
+       (write-region nil nil res-file nil 'silently)))
+    (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
 
+(declare-function image-size "image.c" (spec &optional pixels frame))
+
 (defun doc-view-set-slice (x y width height)
   "Set the slice of the images that should be displayed.
 You can use this function to tell doc-view not to display the
@@ -661,15 +767,15 @@ and Y) of the slice to display and its WIDTH and HEIGHT.
 See `doc-view-set-slice-using-mouse' for a more convenient way to
 do that.  To reset the slice use `doc-view-reset-slice'."
   (interactive
-   (let* ((size (image-size doc-view-current-image t))
+   (let* ((size (image-size (doc-view-current-image) t))
          (a (read-number (format "Top-left X (0..%d): " (car size))))
          (b (read-number (format "Top-left Y (0..%d): " (cdr size))))
          (c (read-number (format "Width (0..%d): " (- (car size) a))))
          (d (read-number (format "Height (0..%d): " (- (cdr size) b)))))
      (list a b c d)))
-  (setq doc-view-current-slice (list x y width height))
+  (setf (doc-view-current-slice) (list x y width height))
   ;; Redisplay
-  (doc-view-goto-page doc-view-current-page))
+  (doc-view-goto-page (doc-view-current-page)))
 
 (defun doc-view-set-slice-using-mouse ()
   "Set the slice of the images that should be displayed.
@@ -694,9 +800,9 @@ dragging it to its bottom-right corner.  See also
   "Reset the current slice.
 After calling this function whole pages will be visible again."
   (interactive)
-  (setq doc-view-current-slice nil)
+  (setf (doc-view-current-slice) nil)
   ;; Redisplay
-  (doc-view-goto-page doc-view-current-page))
+  (doc-view-goto-page (doc-view-current-page)))
 
 ;;;; Display
 
@@ -706,23 +812,39 @@ ARGS is a list of image descriptors."
   (when doc-view-pending-cache-flush
     (clear-image-cache)
     (setq doc-view-pending-cache-flush nil))
-  (if (null file)
-      ;; We're trying to display a page that doesn't exist.  Typically happens
-      ;; if the conversion process somehow failed.  Better not signal an
-      ;; error here because it could prevent a subsequent reconversion from
-      ;; fixing the problem.
-      (progn
-        (setq doc-view-current-image nil)
-        (move-overlay doc-view-current-overlay (point-min) (point-max))
-        (overlay-put doc-view-current-overlay 'display
-                     "Cannot display this page!  Probably a conversion failure!"))
-    (let ((image (apply 'create-image file 'png nil args)))
-      (setq doc-view-current-image image)
-      (move-overlay doc-view-current-overlay (point-min) (point-max))
-      (overlay-put doc-view-current-overlay 'display
-                   (if doc-view-current-slice
-                       (list (cons 'slice doc-view-current-slice) image)
-                     image)))))
+  (let ((ol (doc-view-current-overlay))
+        (image (if (and file (file-readable-p file))
+                   (apply 'create-image file 'png nil args)))
+        (slice (doc-view-current-slice)))
+    (setf (doc-view-current-image) image)
+    (move-overlay ol (point-min) (point-max))
+    (overlay-put ol 'display
+                 (cond
+                  (image
+                   (if slice
+                       (list (cons 'slice slice) image)
+                     image))
+                  ;; We're trying to display a page that doesn't exist.
+                  (doc-view-current-converter-processes
+                   ;; Maybe the page doesn't exist *yet*.
+                   "Cannot display this page (yet)!")
+                  (t
+                   ;; Typically happens if the conversion process somehow
+                   ;; failed.  Better not signal an error here because it
+                   ;; could prevent a subsequent reconversion from fixing
+                   ;; the problem.
+                   (concat "Cannot display this page!\n"
+                           "Maybe because of a conversion failure!"))))
+    (let ((win (overlay-get ol 'window)))
+      (if (stringp (overlay-get ol 'display))
+          (progn            ;Make sure the text is not scrolled out of view.
+            (set-window-hscroll win 0)
+            (set-window-vscroll win 0))
+        (let ((hscroll (image-mode-window-get 'hscroll win))
+              (vscroll (image-mode-window-get 'vscroll win)))
+          ;; Reset scroll settings, in case they were changed.
+          (if hscroll (set-window-hscroll win hscroll))
+          (if vscroll (set-window-vscroll win vscroll)))))))
 
 (defun doc-view-sort (a b)
   "Return non-nil if A should be sorted before B.
@@ -736,21 +858,29 @@ Predicate for sorting `doc-view-current-files'."
 If FORCE is non-nil, start viewing even if the document does not
 have the page we want to view."
   (with-current-buffer buffer
-    (setq doc-view-current-files
-          (sort (directory-files (doc-view-current-cache-dir) t
-                                 "page-[0-9]+\\.png" t)
-                'doc-view-sort))
-    (when (or force
-              (>= (length doc-view-current-files)
-                  (or doc-view-current-page 1)))
-      (doc-view-goto-page doc-view-current-page))))
+    (let ((prev-pages doc-view-current-files))
+      (setq doc-view-current-files
+            (sort (directory-files (doc-view-current-cache-dir) t
+                                   "page-[0-9]+\\.png" t)
+                  'doc-view-sort))
+      (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
   ;; case it's better to keep displaying the "stale" page while computing
   ;; the fresh new ones).
-  (unless (overlay-get doc-view-current-overlay 'display)
-    (overlay-put doc-view-current-overlay 'display
+  (unless (overlay-get (doc-view-current-overlay) 'display)
+    (overlay-put (doc-view-current-overlay) 'display
                  (concat (propertize "Welcome to DocView!" 'face 'bold)
                          "\n"
                          "
@@ -764,12 +894,25 @@ For now these keys are useful:
 `k' : Kill the conversion process and this buffer.
 `K' : Kill the conversion process.\n"))))
 
+(declare-function tooltip-show "tooltip" (text &optional use-echo-area))
+
 (defun doc-view-show-tooltip ()
   (interactive)
-  (tooltip-show doc-view-current-info))
+  (tooltip-show (doc-view-current-info)))
+
+(defun doc-view-open-text ()
+  "Open a buffer with the current doc's contents as text."
+  (interactive)
+  (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 txt 'doc-view-open-text)))))
 
 ;;;;; Toggle between editing and viewing
 
+
 (defun doc-view-toggle-display ()
   "Toggle between editing a document as text or viewing it."
   (interactive)
@@ -778,7 +921,8 @@ For now these keys are useful:
       (progn
        (doc-view-kill-proc)
        (setq buffer-read-only nil)
-        (remove-overlays (point-min) (point-max) 'doc-view)
+       (remove-overlays (point-min) (point-max) 'doc-view t)
+       (set (make-local-variable 'image-mode-winprops-alist) t)
        ;; Switch to the previously used major mode or fall back to fundamental
        ;; mode.
        (if doc-view-previous-major-mode
@@ -793,6 +937,7 @@ For now these keys are useful:
 
 ;;;; Searching
 
+
 (defun doc-view-search-internal (regexp file)
   "Return a list of FILE's pages that contain text matching REGEXP.
 The value is an alist of the form (PAGE CONTEXTS) where PAGE is
@@ -808,10 +953,10 @@ the pagenumber and CONTEXTS are all lines of text containing a match."
        (when (match-string 2)
          (if (/= page lastpage)
              (push (cons page
-                          (list (buffer-substring
-                                 (line-beginning-position)
-                                 (line-end-position))))
-                    matches)
+                         (list (buffer-substring
+                                (line-beginning-position)
+                                (line-end-position))))
+                   matches)
            (setq matches (cons
                           (append
                            (or
@@ -864,32 +1009,15 @@ 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.")
-         (let ((ext (file-name-extension doc-view-buffer-file-name)))
-           (cond
-            ((string= ext "pdf")
-             ;; Doc is a PDF, so convert it to TXT
-             (doc-view-pdf->txt doc-view-buffer-file-name txt))
-            ((string= ext "ps")
-             ;; Doc is a PS, so convert it to PDF (which will be converted to
-             ;; TXT thereafter).
-             (doc-view-ps->pdf doc-view-buffer-file-name
-                               (expand-file-name "doc.pdf"
-                                                 (doc-view-current-cache-dir))))
-            ((string= ext "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))
-            (t (error "DocView doesn't know what to do")))))))))
+         (doc-view-doc->txt txt (lambda () (doc-view-search nil))))))))
 
 (defun doc-view-search-next-match (arg)
   "Go to the ARGth next matching page."
   (interactive "p")
   (let* ((next-pages (doc-view-remove-if
-                     (lambda (i) (<= (car i) doc-view-current-page))
+                     (lambda (i) (<= (car i) (doc-view-current-page)))
                      doc-view-current-search-matches))
         (page (car (nth (1- arg) next-pages))))
     (if page
@@ -903,7 +1031,7 @@ If BACKWARD is non-nil, jump to the previous match."
   "Go to the ARGth previous matching page."
   (interactive "p")
   (let* ((prev-pages (doc-view-remove-if
-                     (lambda (i) (>= (car i) doc-view-current-page))
+                     (lambda (i) (>= (car i) (doc-view-current-page)))
                      doc-view-current-search-matches))
         (page (car (nth (1- arg) (nreverse prev-pages)))))
     (if page
@@ -917,15 +1045,30 @@ If BACKWARD is non-nil, jump to the previous match."
 
 ;; (put 'doc-view-mode 'mode-class 'special)
 
+(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))
+       (> (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)
-       (setq doc-view-current-page (or doc-view-current-page 1))
-       (if (file-exists-p (doc-view-current-cache-dir))
+       (setf (doc-view-current-page) (or (doc-view-current-page) 1))
+       (if (doc-view-already-converted-p)
            (progn
              (message "DocView: using cached files!")
+             ;; Load the saved resolution
+             (let ((res-file (expand-file-name "resolution.el"
+                                               (doc-view-current-cache-dir)))
+                   (res doc-view-resolution))
+               (with-temp-buffer
+                 (when (file-exists-p res-file)
+                   (insert-file-contents res-file)
+                   (setq res (read (current-buffer)))))
+               (when (numberp res)
+                 (set (make-local-variable 'doc-view-resolution) res)))
              (doc-view-display (current-buffer) 'force))
          (doc-view-convert-current-doc))
        (message
@@ -935,12 +1078,16 @@ If BACKWARD is non-nil, jump to the previous match."
                  "editing or viewing the document."))))
     (message
      "%s"
-     (substitute-command-keys
-      (concat "No image (png) support available or some conversion utility for "
-             (file-name-extension doc-view-buffer-file-name)" files is missing.  "
-             "Type \\[doc-view-toggle-display] to switch to an editing mode.")))))
+     (concat "No PNG support is available, or some conversion utility for "
+            (file-name-extension doc-view-buffer-file-name)
+            " files is missing."))
+    (if (and (executable-find doc-view-pdftotext-program)
+            (y-or-n-p
+             "Unable to render file.  View extracted text instead? "))
+       (doc-view-open-text)
+      (doc-view-toggle-display))))
 
-(defvar bookmark-make-cell-function)
+(defvar bookmark-make-record-function)
 
 (defun doc-view-clone-buffer-hook ()
   ;; FIXME: There are several potential problems linked with reconversion
@@ -949,92 +1096,120 @@ If BACKWARD is non-nil, jump to the previous match."
   ;; for each clone), but that means that clones need to collaborate a bit.
   ;; I guess it mostly means: detect when a reconversion process is already
   ;; running, and run the sentinel in all clones.
-  ;; Not sure how important it is to fix it: a better target would be to
-  ;; allow a single buffer (without cloning) to display different pages in
-  ;; different windows.
-  ;; Maybe then the clones should really have a separate /tmp directory
+  ;;
+  ;; Maybe the clones should really have a separate /tmp directory
   ;; so they could have a different resolution and you could use clones
   ;; for zooming.
-  (dolist (ol (overlays-in (point-min) (point-max)))
-    ;; The overlay was copied by the cloning, so we just need to find it
-    ;; and put it in doc-view-current-overlay.
-    (if (overlay-get ol 'doc-view) (setq doc-view-current-overlay ol))))
+  (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.
+
+DocView Mode is an Emacs document viewer.  It displays PDF, PS
+and DVI files (as PNG images) in Emacs buffers.
+
 You can use \\<doc-view-mode-map>\\[doc-view-toggle-display] to
 toggle between displaying the document or editing it as text.
 \\{doc-view-mode-map}"
   (interactive)
 
-  (let* ((prev-major-mode (if (eq major-mode 'doc-view-mode)
-                             doc-view-previous-major-mode
-                           major-mode)))
-    (kill-all-local-variables)
-    (set (make-local-variable 'doc-view-previous-major-mode) prev-major-mode))
+  (if (= (point-min) (point-max))
+      ;; The doc is empty or doesn't exist at all, so fallback to
+      ;; another mode.  We used to also check file-exists-p, but this
+      ;; returns nil for tar members.
+      (let ((auto-mode-alist (remq (rassq 'doc-view-mode auto-mode-alist)
+                                  auto-mode-alist)))
+       (normal-mode))
+
+    (let* ((prev-major-mode (if (eq major-mode 'doc-view-mode)
+                               doc-view-previous-major-mode
+                             major-mode)))
+      (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")))))
 
-  ;; Handle compressed files, remote files, files inside archives
-  (set (make-local-variable 'doc-view-buffer-file-name)
-       (cond
-       (jka-compr-really-do-compress
-        (expand-file-name
-         (file-name-nondirectory
-          (file-name-sans-extension buffer-file-name))
-         doc-view-cache-directory))
-        ;; Is the file readable by local processes?
-        ;; We used to use `file-remote-p' but it's unclear what it's
-        ;; supposed to return nil for things like local files accessed via
-        ;; `su' or via file://...
-       ((let ((file-name-handler-alist nil))
-           (not (file-readable-p buffer-file-name)))
-        (expand-file-name
-         (file-name-nondirectory buffer-file-name)
-         doc-view-cache-directory))
-       (t buffer-file-name)))
-  (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-image)
-  (make-local-variable 'doc-view-current-page)
-  (make-local-variable 'doc-view-current-converter-process)
-  (make-local-variable 'doc-view-current-timer)
-  (make-local-variable 'doc-view-current-slice)
-  (make-local-variable 'doc-view-current-cache-dir)
-  (make-local-variable 'doc-view-current-info)
-  (make-local-variable 'doc-view-current-search-matches)
-  (set (make-local-variable 'doc-view-current-overlay)
-       (make-overlay (point-min) (point-max) nil t))
-  (overlay-put doc-view-current-overlay 'doc-view t)
-  (add-hook 'change-major-mode-hook
-           (lambda () (remove-overlays (point-min) (point-max) 'doc-view))
-           nil t)
-  (add-hook 'clone-indirect-buffer-hook 'doc-view-clone-buffer-hook nil t)
-
-  ;; Keep track of [vh]scroll when switching buffers
-  (make-local-variable 'image-mode-current-hscroll)
-  (make-local-variable 'image-mode-current-vscroll)
-  (image-set-window-hscroll (selected-window) (window-hscroll))
-  (image-set-window-vscroll (selected-window) (window-vscroll))
-  (add-hook 'window-configuration-change-hook
-           'image-reset-current-vhscroll nil t)
-
-  (set (make-local-variable 'mode-line-position)
-       '(" P" (:eval (number-to-string doc-view-current-page))
-        "/" (:eval (number-to-string (length doc-view-current-files)))))
-  ;; Don't scroll unless the user specifically asked for it.
-  (set (make-local-variable 'auto-hscroll-mode) nil)
-  (set (make-local-variable 'cursor-type) nil)
-  (use-local-map doc-view-mode-map)
-  (set (make-local-variable 'after-revert-hook) 'doc-view-reconvert-doc)
-  (set (make-local-variable 'bookmark-make-cell-function)
-       'doc-view-bookmark-make-cell)
-  (setq mode-name "DocView"
-       buffer-read-only t
-       major-mode 'doc-view-mode)
-  (doc-view-initiate-display)
-  (run-mode-hooks 'doc-view-mode-hook))
+    (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)
+        (cond
+         (jka-compr-really-do-compress
+          (expand-file-name
+           (file-name-nondirectory
+            (file-name-sans-extension buffer-file-name))
+           doc-view-cache-directory))
+         ;; Is the file readable by local processes?
+         ;; We used to use `file-remote-p' but it's unclear what it's
+         ;; supposed to return nil for things like local files accessed via
+         ;; `su' or via file://...
+         ((let ((file-name-handler-alist nil))
+            (not (file-readable-p buffer-file-name)))
+          (expand-file-name
+           (file-name-nondirectory buffer-file-name)
+           doc-view-cache-directory))
+         (t buffer-file-name)))
+    (when (not (string= doc-view-buffer-file-name buffer-file-name))
+      (write-region nil nil doc-view-buffer-file-name))
+
+    (add-hook 'change-major-mode-hook
+             (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-hook '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,
+    ;; ...)  for each window in which this document is shown.
+    (add-hook 'image-mode-new-window-functions
+             'doc-view-new-window-function nil t)
+    (image-mode-setup-winprops)
+
+    (set (make-local-variable 'mode-line-position)
+        '(" P" (:eval (number-to-string (doc-view-current-page)))
+          "/" (:eval (number-to-string (length doc-view-current-files)))))
+    ;; Don't scroll unless the user specifically asked for it.
+    (set (make-local-variable 'auto-hscroll-mode) nil)
+    (set (make-local-variable 'cursor-type) nil)
+    (use-local-map doc-view-mode-map)
+    (set (make-local-variable 'after-revert-hook) 'doc-view-reconvert-doc)
+    (set (make-local-variable 'bookmark-make-record-function)
+        'doc-view-bookmark-make-record)
+    (setq mode-name "DocView"
+         buffer-read-only t
+         major-mode 'doc-view-mode)
+    (doc-view-initiate-display)
+    (run-mode-hooks 'doc-view-mode-hook)))
 
 ;;;###autoload
 (define-minor-mode doc-view-minor-mode
@@ -1053,8 +1228,7 @@ See the command `doc-view-mode' for more information on this mode."
 (defun doc-view-clear-cache ()
   "Delete the whole cache (`doc-view-cache-directory')."
   (interactive)
-  (dired-delete-file doc-view-cache-directory 'always)
-  (make-directory doc-view-cache-directory))
+  (dired-delete-file doc-view-cache-directory 'always))
 
 (defun doc-view-dired-cache ()
   "Open `dired' in `doc-view-cache-directory'."
@@ -1064,37 +1238,29 @@ See the command `doc-view-mode' for more information on this mode."
 
 ;;;; Bookmark integration
 
-(defun doc-view-bookmark-make-cell (annotation &rest args)
-  (let ((the-record
-         `((filename . ,buffer-file-name)
-           (page     . ,doc-view-current-page)
-           (handler  . doc-view-bookmark-jump))))
-
-    ;; Take no chances with text properties
-    (set-text-properties 0 (length annotation) nil annotation)
-
-    (when annotation
-      (nconc the-record (list (cons 'annotation annotation))))
-
-    ;; Finally, return the completed record.
-    the-record))
+(declare-function bookmark-make-record-default "bookmark"
+                  (&optional point-only))
+(declare-function bookmark-prop-get "bookmark" (bookmark prop))
+(declare-function bookmark-default-handler "bookmark" (bmk))
 
+(defun doc-view-bookmark-make-record ()
+  (nconc (bookmark-make-record-default)
+         `((page     . ,(doc-view-current-page))
+           (handler  . doc-view-bookmark-jump))))
 
-(declare-function bookmark-get-filename        "bookmark" (bookmark))
-(declare-function bookmark-get-bookmark-record "bookmark" (bookmark))
 
 ;;;###autoload
 (defun doc-view-bookmark-jump (bmk)
   ;; This implements the `handler' function interface for record type
-  ;; returned by `bookmark-make-cell-function', which see.
-  (save-window-excursion
-    (let ((filename (bookmark-get-filename bmk))
-         (page (cdr (assq 'page (bookmark-get-bookmark-record bmk)))))
-      (find-file filename)
+  ;; returned by `doc-view-bookmark-make-record', which see.
+  (prog1 (bookmark-default-handler bmk)
+    (let ((page (bookmark-prop-get bmk 'page)))
       (when (not (eq major-mode 'doc-view-mode))
-       (doc-view-toggle-display))
-      (doc-view-goto-page page)
-      `((buffer ,(current-buffer)) (position ,1)))))
+        (doc-view-toggle-display))
+      (with-selected-window
+          (or (get-buffer-window (current-buffer) 0)
+              (selected-window))
+        (doc-view-goto-page page)))))
 
 
 (provide 'doc-view)