]> code.delx.au - gnu-emacs/blobdiff - lisp/doc-view.el
Make autoloading commands prompt for autoload file (Bug#7989)
[gnu-emacs] / lisp / doc-view.el
index bfdc40d50b0486eafd49e3d6093ff02db0a6b6be..ab0d6bf837b170283ee1fd7da88235f8023a4556 100644 (file)
@@ -1,6 +1,7 @@
-;;; doc-view.el --- View PDF/PostScript/DVI files in Emacs
+;;; doc-view.el --- View PDF/PostScript/DVI files in Emacs -*- lexical-binding: t -*-
 
-;; Copyright (C) 2007, 2008, 2009 Free Software Foundation, Inc.
+
+;; Copyright (C) 2007-2011 Free Software Foundation, Inc.
 ;;
 ;; Author: Tassilo Horn <tassilo@member.fsf.org>
 ;; Maintainer: Tassilo Horn <tassilo@member.fsf.org>
   :link '(function-link doc-view)
   :version "22.2"
   :group 'applications
+  :group 'data
   :group 'multimedia
   :prefix "doc-view-")
 
 
 (defcustom doc-view-ghostscript-options
   '("-dSAFER" ;; Avoid security problems when rendering files from untrusted
-             ;; sources.
+    ;; sources.
     "-dNOPAUSE" "-sDEVICE=png16m" "-dTextAlphaBits=4"
     "-dBATCH" "-dGraphicsAlphaBits=4" "-dQUIET")
   "A list of options to give to ghostscript."
@@ -167,6 +169,12 @@ Higher values result in larger images."
   :type 'number
   :group 'doc-view)
 
+(defcustom doc-view-image-width 850
+  "Default image width.
+Has only an effect if imagemagick support is compiled into emacs."
+  :type 'number
+  :group 'doc-view)
+
 (defcustom doc-view-dvipdfm-program (executable-find "dvipdfm")
   "Program to convert DVI files to PDF.
 
@@ -189,6 +197,13 @@ If this and `doc-view-dvipdfm-program' are set,
   :type 'file
   :group 'doc-view)
 
+(defcustom doc-view-unoconv-program (executable-find "unoconv")
+  "Program to convert any file type readable by OpenOffice.org to PDF.
+
+Needed for viewing OpenOffice.org (and MS Office) files."
+  :type 'file
+  :group 'doc-view)
+
 (defcustom doc-view-ps2pdf-program (executable-find "ps2pdf")
   "Program to convert PS files to PDF.
 
@@ -222,12 +237,28 @@ has finished."
   :type 'integer
   :group 'doc-view)
 
+(defcustom doc-view-continuous nil
+  "In Continuous mode reaching the page edge advances to next/previous page.
+When non-nil, scrolling a line upward at the bottom edge of the page
+moves to the next page, and scrolling a line downward at the top edge
+of the page moves to the previous page."
+  :type 'boolean
+  :group 'doc-view
+  :version "23.2")
+
 ;;;; Internal Variables
 
 (defun doc-view-new-window-function (winprops)
   (let ((ol (image-mode-window-get 'overlay winprops)))
+    (when (and ol (not (overlay-buffer ol)))
+      ;; I've seen `ol' be a dead overlay.  I do not yet know how this
+      ;; happened, so maybe the bug is elsewhere, but in the mean time,
+      ;; this seems like a safe approach.
+      (setq ol nil))
     (if ol
-        (setq ol (copy-overlay ol))
+        (progn
+          (assert (eq (overlay-buffer ol) (current-buffer)))
+          (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))
@@ -286,12 +317,21 @@ Can be `dvi', `pdf', or `ps'.")
     (define-key map [remap backward-page] 'doc-view-previous-page)
     (define-key map (kbd "SPC")       'doc-view-scroll-up-or-next-page)
     (define-key map (kbd "DEL")       'doc-view-scroll-down-or-previous-page)
+    (define-key map (kbd "C-n")       'doc-view-next-line-or-next-page)
+    (define-key map (kbd "<down>")    'doc-view-next-line-or-next-page)
+    (define-key map (kbd "C-p")       'doc-view-previous-line-or-previous-page)
+    (define-key map (kbd "<up>")      'doc-view-previous-line-or-previous-page)
     (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 (kbd "RET")       'image-next-line)
     ;; Zoom in/out.
     (define-key map "+"               'doc-view-enlarge)
     (define-key map "-"               'doc-view-shrink)
+    ;; Fit the image to the window
+    (define-key map "W"               'doc-view-fit-width-to-window)
+    (define-key map "H"               'doc-view-fit-height-to-window)
+    (define-key map "P"               'doc-view-fit-page-to-window)
     ;; 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)
@@ -309,22 +349,41 @@ Can be `dvi', `pdf', or `ps'.")
     (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)
+    ;; Reconvert the current document.  Don't just use revert-buffer
+    ;; because that resets the scale factor, the page number, ...
+    (define-key map (kbd "g")         'doc-view-revert-buffer)
+    (define-key map (kbd "r")         'doc-view-revert-buffer)
     map)
   "Keymap used by `doc-view-mode' when displaying a doc as a set of images.")
 
+(defun doc-view-revert-buffer (&optional ignore-auto noconfirm)
+  "Like `revert-buffer', but preserves the buffer's current modes."
+  ;; FIXME: this should probably be moved to files.el and used for
+  ;; most/all "g" bindings to revert-buffer.
+  (interactive (list (not current-prefix-arg)))
+  (revert-buffer ignore-auto noconfirm 'preserve-modes))
+
+
 (easy-menu-define doc-view-menu doc-view-mode-map
   "Menu for Doc View mode."
   '("DocView"
+    ["Toggle display"          doc-view-toggle-display]
+    ("Continuous"
+     ["Off"                     (setq doc-view-continuous nil)
+      :style radio :selected    (eq doc-view-continuous nil)]
+     ["On"                     (setq doc-view-continuous t)
+      :style radio :selected    (eq doc-view-continuous t)]
+     "---"
+     ["Save as Default"
+      (customize-save-variable 'doc-view-continuous doc-view-continuous) t]
+     )
+    "---"
     ["Set Slice"               doc-view-set-slice-using-mouse]
     ["Set Slice (manual)"      doc-view-set-slice]
     ["Reset Slice"             doc-view-reset-slice]
     "---"
     ["Search"                  doc-view-search]
     ["Search Backwards"         doc-view-search-backward]
-    ["Toggle display"          doc-view-toggle-display]
     ))
 
 (defvar doc-view-minor-mode-map
@@ -343,10 +402,13 @@ Can be `dvi', `pdf', or `ps'.")
 (defmacro doc-view-current-image () `(image-mode-window-get 'image))
 (defmacro doc-view-current-slice () `(image-mode-window-get 'slice))
 
+(defun doc-view-last-page-number ()
+  (length doc-view-current-files))
+
 (defun doc-view-goto-page (page)
   "View the page given by PAGE."
   (interactive "nPage: ")
-  (let ((len (length doc-view-current-files))
+  (let ((len (doc-view-last-page-number))
        (hscroll (window-hscroll)))
     (if (< page 1)
        (setq page 1)
@@ -385,15 +447,16 @@ Can be `dvi', `pdf', or `ps'.")
                  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)))
+                             (let ((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)
+                                      ;; Make sure we don't infloop.
+                                      (file-readable-p file)
                                       (with-selected-window win
-                                        (doc-view-goto-page page))))))))
+                                                           (doc-view-goto-page page))))))))
     (overlay-put (doc-view-current-overlay)
                  'help-echo (doc-view-current-info))))
 
@@ -415,38 +478,80 @@ Can be `dvi', `pdf', or `ps'.")
 (defun doc-view-last-page ()
   "View the last page."
   (interactive)
-  (doc-view-goto-page (length doc-view-current-files)))
+  (doc-view-goto-page (doc-view-last-page-number)))
 
-(defun doc-view-scroll-up-or-next-page ()
-  "Scroll page up if possible, else goto next page."
-  (interactive)
-  (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))
-       (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)
-  (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-eob)
-       (image-bol 1))
-      (set-window-hscroll (selected-window) hscroll))))
+(defun doc-view-scroll-up-or-next-page (&optional arg)
+  "Scroll page up ARG lines if possible, else goto next page.
+When `doc-view-continuous' is non-nil, scrolling upward
+at the bottom edge of the page moves to the next page.
+Otherwise, goto next page only on typing SPC (ARG is nil)."
+  (interactive "P")
+  (if (or doc-view-continuous (null arg))
+      (let ((hscroll (window-hscroll))
+           (cur-page (doc-view-current-page)))
+       (when (= (window-vscroll) (image-scroll-up arg))
+         (doc-view-next-page)
+         (when (/= cur-page (doc-view-current-page))
+           (image-bob)
+           (image-bol 1))
+         (set-window-hscroll (selected-window) hscroll)))
+    (image-scroll-up arg)))
+
+(defun doc-view-scroll-down-or-previous-page (&optional arg)
+  "Scroll page down ARG lines if possible, else goto previous page.
+When `doc-view-continuous' is non-nil, scrolling downward
+at the top edge of the page moves to the previous page.
+Otherwise, goto previous page only on typing DEL (ARG is nil)."
+  (interactive "P")
+  (if (or doc-view-continuous (null arg))
+      (let ((hscroll (window-hscroll))
+           (cur-page (doc-view-current-page)))
+       (when (= (window-vscroll) (image-scroll-down arg))
+         (doc-view-previous-page)
+         (when (/= cur-page (doc-view-current-page))
+           (image-eob)
+           (image-bol 1))
+         (set-window-hscroll (selected-window) hscroll)))
+    (image-scroll-down arg)))
+
+(defun doc-view-next-line-or-next-page (&optional arg)
+  "Scroll upward by ARG lines if possible, else goto next page.
+When `doc-view-continuous' is non-nil, scrolling a line upward
+at the bottom edge of the page moves to the next page."
+  (interactive "p")
+  (if doc-view-continuous
+      (let ((hscroll (window-hscroll))
+           (cur-page (doc-view-current-page)))
+       (when (= (window-vscroll) (image-next-line arg))
+         (doc-view-next-page)
+         (when (/= cur-page (doc-view-current-page))
+           (image-bob)
+           (image-bol 1))
+         (set-window-hscroll (selected-window) hscroll)))
+    (image-next-line 1)))
+
+(defun doc-view-previous-line-or-previous-page (&optional arg)
+  "Scroll downward by ARG lines if possible, else goto previous page.
+When `doc-view-continuous' is non-nil, scrolling a line downward
+at the top edge of the page moves to the previous page."
+  (interactive "p")
+  (if doc-view-continuous
+      (let ((hscroll (window-hscroll))
+           (cur-page (doc-view-current-page)))
+       (when (= (window-vscroll) (image-previous-line arg))
+         (doc-view-previous-page)
+         (when (/= cur-page (doc-view-current-page))
+           (image-eob)
+           (image-bol 1))
+         (set-window-hscroll (selected-window) hscroll)))
+    (image-previous-line arg)))
 
 ;;;; Utility Functions
 
 (defun doc-view-kill-proc ()
   "Kill the current converter process(es)."
   (interactive)
-  (while doc-view-current-converter-processes
+  (while (consp 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
@@ -497,7 +602,7 @@ It's a subdirectory of `doc-view-cache-directory'."
     (setq doc-view-current-cache-dir
          (file-name-as-directory
           (expand-file-name
-           (concat (file-name-nondirectory buffer-file-name)
+           (concat (file-name-nondirectory doc-view-buffer-file-name)
                    "-"
                    (let ((file doc-view-buffer-file-name))
                      (with-temp-buffer
@@ -515,10 +620,12 @@ It's a subdirectory of `doc-view-cache-directory'."
 
 ;;;###autoload
 (defun doc-view-mode-p (type)
-  "Return non-nil if image type TYPE is available for `doc-view'.
-Image types are symbols like `dvi', `postscript' or `pdf'."
+  "Return non-nil if document type TYPE is available for `doc-view'.
+Document types are symbols like `dvi', `ps', `pdf', or `odf' (any
+OpenDocument format)."
   (and (display-graphic-p)
-       (image-type-available-p 'png)
+       (or (image-type-available-p 'imagemagick)
+          (image-type-available-p 'png))
        (cond
        ((eq type 'dvi)
         (and (doc-view-mode-p 'pdf)
@@ -530,6 +637,10 @@ Image types are symbols like `dvi', `postscript' or `pdf'."
             (eq type 'pdf))
         (and doc-view-ghostscript-program
              (executable-find doc-view-ghostscript-program)))
+       ((eq type 'odf)
+        (and doc-view-unoconv-program
+             (executable-find doc-view-unoconv-program)
+             (doc-view-mode-p 'pdf)))
        (t ;; unknown image type
         nil))))
 
@@ -540,15 +651,95 @@ Image types are symbols like `dvi', `postscript' or `pdf'."
 (defun doc-view-enlarge (factor)
   "Enlarge the document."
   (interactive (list doc-view-shrink-factor))
-  (set (make-local-variable 'doc-view-resolution)
-       (* factor doc-view-resolution))
-  (doc-view-reconvert-doc))
+  (if (eq (plist-get (cdr (doc-view-current-image)) :type)
+         'imagemagick)
+      ;; ImageMagick supports on-the-fly-rescaling
+      (progn
+       (set (make-local-variable 'doc-view-image-width)
+            (ceiling (* factor doc-view-image-width)))
+       (doc-view-insert-image (plist-get (cdr (doc-view-current-image)) :file)
+                              :width doc-view-image-width))
+    (set (make-local-variable 'doc-view-resolution)
+        (ceiling (* factor doc-view-resolution)))
+    (doc-view-reconvert-doc)))
 
 (defun doc-view-shrink (factor)
   "Shrink the document."
   (interactive (list doc-view-shrink-factor))
   (doc-view-enlarge (/ 1.0 factor)))
 
+(defun doc-view-fit-width-to-window ()
+  "Fit the image width to the window width."
+  (interactive)
+  (let ((win-width (- (nth 2 (window-inside-pixel-edges))
+                      (nth 0 (window-inside-pixel-edges))))
+        (slice (doc-view-current-slice)))
+    (if (not slice)
+        (let ((img-width (car (image-display-size
+                               (image-get-display-property) t))))
+          (doc-view-enlarge (/ (float win-width) (float img-width))))
+
+      ;; If slice is set
+      (let* ((slice-width (nth 2 slice))
+             (scale-factor (/ (float win-width) (float slice-width)))
+             (new-slice (mapcar (lambda (x) (ceiling (* scale-factor x))) slice)))
+
+        (doc-view-enlarge scale-factor)
+        (setf (doc-view-current-slice) new-slice)
+        (doc-view-goto-page (doc-view-current-page))))))
+
+(defun doc-view-fit-height-to-window ()
+  "Fit the image height to the window height."
+  (interactive)
+  (let ((win-height (- (nth 3 (window-inside-pixel-edges))
+                       (nth 1 (window-inside-pixel-edges))))
+        (slice (doc-view-current-slice)))
+    (if (not slice)
+        (let ((img-height (cdr (image-display-size
+                                (image-get-display-property) t))))
+          ;; When users call 'doc-view-fit-height-to-window',
+          ;; they might want to go to next page by typing SPC
+          ;; ONLY once. So I used '(- win-height 1)' instead of
+          ;; 'win-height'
+          (doc-view-enlarge (/ (float (- win-height 1)) (float img-height))))
+
+      ;; If slice is set
+      (let* ((slice-height (nth 3 slice))
+             (scale-factor (/ (float (- win-height 1)) (float slice-height)))
+             (new-slice (mapcar (lambda (x) (ceiling (* scale-factor x))) slice)))
+
+        (doc-view-enlarge scale-factor)
+        (setf (doc-view-current-slice) new-slice)
+        (doc-view-goto-page (doc-view-current-page))))))
+
+(defun doc-view-fit-page-to-window ()
+  "Fit the image to the window.
+More specifically, this function enlarges image by:
+
+min {(window-width / image-width), (window-height / image-height)} times."
+  (interactive)
+  (let ((win-width (- (nth 2 (window-inside-pixel-edges))
+                      (nth 0 (window-inside-pixel-edges))))
+        (win-height (- (nth 3 (window-inside-pixel-edges))
+                       (nth 1 (window-inside-pixel-edges))))
+        (slice (doc-view-current-slice)))
+    (if (not slice)
+        (let ((img-width (car (image-display-size
+                               (image-get-display-property) t)))
+              (img-height (cdr (image-display-size
+                                (image-get-display-property) t))))
+          (doc-view-enlarge (min (/ (float win-width) (float img-width))
+                                 (/ (float (- win-height 1)) (float img-height)))))
+      ;; If slice is set
+      (let* ((slice-width (nth 2 slice))
+             (slice-height (nth 3 slice))
+             (scale-factor (min (/ (float win-width) (float slice-width))
+                                (/ (float (- win-height 1)) (float slice-height))))
+             (new-slice (mapcar (lambda (x) (ceiling (* scale-factor x))) slice)))
+        (doc-view-enlarge scale-factor)
+        (setf (doc-view-current-slice) new-slice)
+        (doc-view-goto-page (doc-view-current-page))))))
+
 (defun doc-view-reconvert-doc ()
   "Reconvert the current document.
 Should be invoked when the cached images aren't up-to-date."
@@ -556,14 +747,17 @@ Should be invoked when the cached images aren't up-to-date."
   (doc-view-kill-proc)
   ;; Clear the old cached files
   (when (file-exists-p (doc-view-current-cache-dir))
-    (dired-delete-file (doc-view-current-cache-dir) 'always))
+    (delete-directory (doc-view-current-cache-dir) 'recursive))
   (doc-view-initiate-display))
 
 (defun doc-view-sentinel (proc event)
   "Generic sentinel for doc-view conversion processes."
   (if (not (string-match "finished" event))
       (message "DocView: process %s changed status to %s."
-               (process-name proc) event)
+               (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
@@ -594,12 +788,19 @@ Should be invoked when the cached images aren't up-to-date."
   (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)
+                             (list dvi pdf)
+                             callback)
     (doc-view-start-process "dvi->pdf" doc-view-dvipdfm-program
                            (list "-o" pdf dvi)
                            callback)))
 
+(defun doc-view-odf->pdf (odf callback)
+  "Convert ODF to PDF asynchronously and call CALLBACK when finished.
+The converted PDF is put into the current cache directory, and it
+is named like ODF with the extension turned to pdf."
+  (doc-view-start-process "odf->pdf" doc-view-unoconv-program
+                         (list "-f" "pdf" "-o" (doc-view-current-cache-dir) odf)
+                         callback))
 
 (defun doc-view-pdf/ps->png (pdf-ps png)
   "Convert PDF-PS to PNG asynchronously."
@@ -609,11 +810,18 @@ Should be invoked when the cached images aren't up-to-date."
            (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)))
+   (let ((resolution doc-view-resolution))
+     (lambda ()
+       ;; Only create the resolution file when it's all done, so it also
+       ;; serves as a witness that the conversion is complete.
+       (write-region (prin1-to-string resolution) nil
+                     (expand-file-name "resolution.el"
+                                       (doc-view-current-cache-dir))
+                     nil 'silently)
+       (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
@@ -647,7 +855,7 @@ Start by converting PAGES, and then the rest."
     ;; (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)))
+    (let ((rest (cdr pages)))
       (doc-view-pdf->png-1
        pdf (format png (car pages)) (car pages)
        (lambda ()
@@ -655,6 +863,13 @@ Start by converting PAGES, and then the rest."
              (doc-view-pdf->png pdf png rest)
            ;; Yippie, the important pages are done, update the display.
            (clear-image-cache)
+           ;; For the windows that have a message (like "Welcome to
+           ;; DocView") display property, clearing the image cache is
+           ;; not sufficient.
+           (dolist (win (get-buffer-window-list (current-buffer) nil 'visible))
+             (with-selected-window win
+                                  (when (stringp (get-char-property (point-min) 'display))
+                                    (doc-view-goto-page (doc-view-current-page)))))
            ;; Convert the rest of the pages.
            (doc-view-pdf/ps->png pdf png)))))))
 
@@ -676,10 +891,8 @@ Start by converting PAGES, and then the rest."
     (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))
+     (let ((pdf (expand-file-name "doc.pdf"
+                                 (doc-view-current-cache-dir))))
        (doc-view-ps->pdf doc-view-buffer-file-name pdf
                          (lambda () (doc-view-pdf->txt pdf txt callback)))))
     (dvi
@@ -688,6 +901,12 @@ Start by converting PAGES, and then the rest."
      (doc-view-pdf->txt (expand-file-name "doc.pdf"
                                           (doc-view-current-cache-dir))
                         txt callback))
+    (odf
+     ;; Doc is some ODF (or MS Office) doc.  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)
@@ -721,29 +940,37 @@ 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) 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))
-       (write-file res-file)))
     (case doc-view-doc-type
       (dvi
        ;; 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
+                            (lambda () (doc-view-pdf/ps->png pdf png-file)))))
+      (odf
+       ;; ODF 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))
+           (opdf (expand-file-name (concat (file-name-sans-extension
+                                            (file-name-nondirectory doc-view-buffer-file-name))
+                                           ".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)))
+        ;; The unoconv tool only supports a output directory, but no
+        ;; file name.  It's named like the input file with the
+        ;; extension replaced by pdf.
+         (doc-view-odf->pdf doc-view-buffer-file-name
+                            (lambda ()
+                             ;; Rename to doc.pdf
+                             (rename-file opdf pdf)
+                             (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)))))
@@ -808,7 +1035,11 @@ ARGS is a list of image descriptors."
     (setq doc-view-pending-cache-flush nil))
   (let ((ol (doc-view-current-overlay))
         (image (if (and file (file-readable-p file))
-                   (apply 'create-image file 'png nil args)))
+                  (if (not (fboundp 'imagemagick-types))
+                      (apply 'create-image file 'png nil args)
+                    (unless (member :width args)
+                      (setq args (append args (list :width doc-view-image-width))))
+                    (apply 'create-image file 'imagemagick nil args))))
         (slice (doc-view-current-slice)))
     (setf (doc-view-current-image) image)
     (move-overlay ol (point-min) (point-max))
@@ -866,8 +1097,8 @@ have the page we want to view."
                    (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))))))))
+                                 (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
@@ -901,12 +1132,16 @@ For now these keys are useful:
       (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)
+         (let ((name (concat "Text contents of "
+                             (file-name-nondirectory buffer-file-name)))
+               (dir (file-name-directory buffer-file-name)))
+           (with-current-buffer (find-file txt)
+             (rename-buffer name)
+             (setq default-directory dir)))
        (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)
@@ -917,11 +1152,9 @@ For now these keys are useful:
        (setq buffer-read-only nil)
        (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
-           (funcall doc-view-previous-major-mode)
-         (fundamental-mode))
+       ;; Switch to the previously used major mode or fall back to
+       ;; normal mode.
+       (doc-view-fallback-mode)
        (doc-view-minor-mode 1))
     ;; Switch to doc-view-mode
     (when (and (buffer-modified-p)
@@ -1037,12 +1270,18 @@ If BACKWARD is non-nil, jump to the previous match."
 
 ;;;; User interface commands and the mode
 
-;; (put 'doc-view-mode 'mode-class 'special)
+(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)))
+       ;; Check that the resolution info is there, otherwise it means
+       ;; the conversion is incomplete.
+       (file-readable-p (expand-file-name "resolution.el"
+                                          (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
@@ -1054,14 +1293,14 @@ If BACKWARD is non-nil, jump to the previous match."
            (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)
+             (let* ((res-file (expand-file-name "resolution.el"
+                                                 (doc-view-current-cache-dir)))
+                     (res
+                      (with-temp-buffer
+                        (when (file-readable-p res-file)
+                          (insert-file-contents res-file)
+                          (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))
@@ -1072,15 +1311,14 @@ If BACKWARD is non-nil, jump to the previous match."
                  "editing or viewing the document."))))
     (message
      "%s"
-     (substitute-command-keys
-      (concat "No 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 "
-             (if (eq doc-view-doc-type 'ps)
-                 "ps-mode"
-               "fundamental-mode")
-             ", \\[doc-view-open-text] to show the doc as text in a separate buffer "
-             " or \\[doc-view-kill-proc-and-buffer] to kill this buffer.")))))
+     (concat "No PNG support is available, or some conversion utility for "
+            (file-name-extension doc-view-buffer-file-name)
+            " files is missing."))
+    (when (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-record-function)
 
@@ -1103,6 +1341,41 @@ If BACKWARD is non-nil, jump to the previous match."
     (dolist (x l1) (if (memq x l2) (push x l)))
     l))
 
+(defun doc-view-set-doc-type ()
+  "Figure out the current document type (`doc-view-doc-type')."
+  (let ((name-types
+        (when buffer-file-name
+          (cdr (assoc (file-name-extension buffer-file-name)
+                      '(
+                        ;; DVI
+                        ("dvi" dvi)
+                        ;; PDF
+                        ("pdf" pdf) ("epdf" pdf)
+                        ;; PostScript
+                        ("ps" ps) ("eps" ps)
+                        ;; OpenDocument formats
+                        ("odt" odf) ("ods" odf) ("odp" odf) ("odg" odf)
+                        ("odc" odf) ("odi" odf) ("odm" odf) ("ott" odf)
+                        ("ots" odf) ("otp" odf) ("otg" odf)
+                        ;; Microsoft Office formats (also handled
+                        ;; by the odf conversion chain)
+                        ("doc" odf) ("docx" odf) ("xls" odf) ("xlsx" odf)
+                        ("ppt" odf) ("pptx" odf))))))
+       (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"))))))
+
 ;;;###autoload
 (defun doc-view-mode ()
   "Major mode in DocView buffers.
@@ -1115,49 +1388,30 @@ toggle between displaying the document or editing it as text.
 \\{doc-view-mode-map}"
   (interactive)
 
-  (if (or (not (file-exists-p buffer-file-name))
-         (= (point-min) (point-max)))
+  (if (= (point-min) (point-max))
       ;; The doc is empty or doesn't exist at all, so fallback to
-      ;; another mode.
-      (let ((auto-mode-alist (remq (rassq 'doc-view-mode auto-mode-alist)
-                                  auto-mode-alist)))
-       (normal-mode))
+      ;; another mode.  We used to also check file-exists-p, but this
+      ;; returns nil for tar members.
+      (doc-view-fallback-mode)
 
     (let* ((prev-major-mode (if (eq major-mode 'doc-view-mode)
                                doc-view-previous-major-mode
-                             major-mode)))
+                             (when (not (memq major-mode
+                                              '(doc-view-mode fundamental-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")))))
+    (unless doc-view-doc-type
+      (doc-view-set-doc-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)
         (cond
          (jka-compr-really-do-compress
+           ;; FIXME: there's a risk of name conflicts here.
           (expand-file-name
            (file-name-nondirectory
             (file-name-sans-extension buffer-file-name))
@@ -1167,10 +1421,13 @@ toggle between displaying the document or editing it as text.
          ;; 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)))
+            (not (and buffer-file-name (file-readable-p buffer-file-name))))
+           ;; FIXME: there's a risk of name conflicts here.
           (expand-file-name
-           (file-name-nondirectory buffer-file-name)
-           doc-view-cache-directory))
+           (if buffer-file-name
+                (file-name-nondirectory buffer-file-name)
+              (buffer-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))
@@ -1192,9 +1449,13 @@ toggle between displaying the document or editing it as text.
 
     (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)))))
+          "/" (:eval (number-to-string (doc-view-last-page-number)))))
     ;; Don't scroll unless the user specifically asked for it.
     (set (make-local-variable 'auto-hscroll-mode) nil)
+    (set (make-local-variable 'mwheel-scroll-up-function)
+        'doc-view-scroll-up-or-next-page)
+    (set (make-local-variable 'mwheel-scroll-down-function)
+        'doc-view-scroll-down-or-previous-page)
     (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)
@@ -1204,8 +1465,35 @@ toggle between displaying the document or editing it as text.
          buffer-read-only t
          major-mode 'doc-view-mode)
     (doc-view-initiate-display)
+    ;; Switch off view-mode explicitly, because doc-view-mode is the
+    ;; canonical view mode for PDF/PS/DVI files.  This could be
+    ;; switched on automatically depending on the value of
+    ;; `view-read-only'.
+    (set (make-local-variable 'view-read-only) nil)
     (run-mode-hooks 'doc-view-mode-hook)))
 
+(defun doc-view-fallback-mode ()
+  "Fallback to the previous or next best major mode."
+  (if doc-view-previous-major-mode
+      (funcall doc-view-previous-major-mode)
+    (let ((auto-mode-alist (rassq-delete-all
+                           'doc-view-mode-maybe
+                           (rassq-delete-all 'doc-view-mode
+                                             (copy-alist auto-mode-alist)))))
+      (normal-mode))))
+
+;;;###autoload
+(defun doc-view-mode-maybe ()
+  "Switch to `doc-view-mode' if possible.
+If the required external tools are not available, then fallback
+to the next best mode."
+  (condition-case nil
+      (doc-view-set-doc-type)
+    (error (doc-view-fallback-mode)))
+  (if (doc-view-mode-p doc-view-doc-type)
+      (doc-view-mode)
+    (doc-view-fallback-mode)))
+
 ;;;###autoload
 (define-minor-mode doc-view-minor-mode
   "Toggle Doc view minor mode.
@@ -1233,8 +1521,8 @@ See the command `doc-view-mode' for more information on this mode."
 
 ;;;; Bookmark integration
 
-(declare-function bookmark-make-record-default "bookmark"
-                  (&optional point-only))
+(declare-function bookmark-make-record-default
+                  "bookmark" (&optional no-file no-context posn))
 (declare-function bookmark-prop-get "bookmark" (bookmark prop))
 (declare-function bookmark-default-handler "bookmark" (bmk))
 
@@ -1253,9 +1541,9 @@ See the command `doc-view-mode' for more information on this mode."
       (when (not (eq major-mode 'doc-view-mode))
         (doc-view-toggle-display))
       (with-selected-window
-          (or (get-buffer-window (current-buffer) 0)
-              (selected-window))
-        (doc-view-goto-page page)))))
+       (or (get-buffer-window (current-buffer) 0)
+          (selected-window))
+       (doc-view-goto-page page)))))
 
 
 (provide 'doc-view)
@@ -1264,5 +1552,4 @@ See the command `doc-view-mode' for more information on this mode."
 ;; mode: outline-minor
 ;; End:
 
-;; arch-tag: 5d6e5c5e-095f-489e-b4e4-1ca90a7d79be
 ;;; doc-view.el ends here