]> 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 6ebec982c0c0d19b455f16b76c2ba9aff30358ea..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, 2010 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.
 
@@ -235,8 +250,15 @@ of the page moves to the previous page."
 
 (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))
@@ -306,6 +328,10 @@ Can be `dvi', `pdf', or `ps'.")
     ;; 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)
@@ -323,12 +349,21 @@ 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"
@@ -367,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)
@@ -409,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))))
 
@@ -439,7 +478,7 @@ 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 (&optional arg)
   "Scroll page up ARG lines if possible, else goto next page.
@@ -512,7 +551,7 @@ at the top edge of the page moves to the previous page."
 (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
@@ -581,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)
@@ -596,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))))
 
@@ -606,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."
@@ -622,7 +747,7 @@ 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)
@@ -663,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."
@@ -678,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
@@ -716,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 ()
@@ -724,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)))))))
 
@@ -745,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
@@ -757,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)
@@ -790,31 +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))
-        ;; 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.
+       (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)))))
@@ -879,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))
@@ -937,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
@@ -972,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)
@@ -988,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)
@@ -1108,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
@@ -1125,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))
@@ -1146,11 +1314,11 @@ If BACKWARD is non-nil, jump to the previous match."
      (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))))
+    (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)
 
@@ -1173,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.
@@ -1189,39 +1392,19 @@ toggle between displaying the document or editing it as text.
       ;; 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))
+      (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
@@ -1266,7 +1449,7 @@ 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)
@@ -1289,6 +1472,28 @@ toggle between displaying the document or editing it as text.
     (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.
@@ -1316,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))
 
@@ -1336,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)
@@ -1347,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