]> code.delx.au - gnu-emacs/blobdiff - lisp/doc-view.el
Force saving without encoding, regardless of current message.
[gnu-emacs] / lisp / doc-view.el
index 53e7811bad1e3c05505188b32bb2f1678a83506f..30aa3a09bf2ec2141dea7ee6137e099945fa442b 100644 (file)
@@ -1,10 +1,10 @@
 ;;; doc-view.el --- View PDF/PostScript/DVI files in Emacs -*- lexical-binding: t -*-
 
 
-;; Copyright (C) 2007-2012 Free Software Foundation, Inc.
+;; Copyright (C) 2007-2013 Free Software Foundation, Inc.
 ;;
-;; Author: Tassilo Horn <tassilo@member.fsf.org>
-;; Maintainer: Tassilo Horn <tassilo@member.fsf.org>
+;; Author: Tassilo Horn <tsdh@gnu.org>
+;; Maintainer: Tassilo Horn <tsdh@gnu.org>
 ;; Keywords: files, pdf, ps, dvi
 
 ;; This file is part of GNU Emacs.
 ;; pages won't be displayed before conversion of the document finished
 ;; completely.
 ;;
-;; DocView lets you select a slice of the displayed pages.  This slice will be
-;; remembered and applied to all pages of the current document.  This enables
-;; you to cut away the margins of a document to save some space.  To select a
-;; 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 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').
+;; DocView lets you select a slice of the displayed pages.  This slice
+;; will be remembered and applied to all pages of the current
+;; document.  This enables you to cut away the margins of a document
+;; to save some space.  To select a 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 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.
+;; Even more accurate and convenient is to use
+;; `doc-view-set-slice-from-bounding-box' (bound to `s b') which uses
+;; the BoundingBox information of the current page to set an optimal
+;; slice.  To reset the slice use `doc-view-reset-slice' (bound to `s
+;; r').
 ;;
 ;; You can also search within the document.  The command `doc-view-search'
 ;; (bound to `C-s') queries for a search regexp and initializes a list of all
 ;; - 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?
 ;; - 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))
+(eval-when-compile (require 'cl-lib))
 (require 'dired)
 (require 'image-mode)
 (require 'jka-compr)
 ;;;; Customization Options
 
 (defgroup doc-view nil
-  "In-buffer viewer for PDF, PostScript and DVI files."
+  "In-buffer viewer for PDF, PostScript, DVI, and DJVU files."
   :link '(function-link doc-view)
   :version "22.2"
   :group 'applications
   :type 'file
   :group 'doc-view)
 
+(defcustom doc-view-pdfdraw-program
+  (cond
+   ((executable-find "pdfdraw") "pdfdraw")
+   (t "mudraw"))
+  "Name of MuPDF's program to convert PDF files to PNG."
+  :type 'file
+  :version "24.4")
+
+(defcustom doc-view-pdf->png-converter-function
+  (if (executable-find doc-view-pdfdraw-program)
+      #'doc-view-pdf->png-converter-mupdf
+    #'doc-view-pdf->png-converter-ghostscript)
+  "Function to call to convert a PDF file into a PNG file."
+  :type '(radio
+          (function-item doc-view-pdf->png-converter-ghostscript
+                         :doc "Use ghostscript")
+          (function-item doc-view-pdf->png-converter-mupdf
+                         :doc "Use mupdf")
+          function)
+  :version "24.4")
+
 (defcustom doc-view-ghostscript-options
   '("-dSAFER" ;; Avoid security problems when rendering files from untrusted
     ;; sources.
@@ -169,9 +194,17 @@ Higher values result in larger images."
   :type 'number
   :group 'doc-view)
 
+(defcustom doc-view-scale-internally t
+  "Whether we should try to rescale images ourselves.
+If nil, the document is re-rendered every time the scaling factor is modified.
+This only has an effect if the image libraries linked with Emacs support
+scaling."
+  :type 'boolean)
+
 (defcustom doc-view-image-width 850
   "Default image width.
-Has only an effect if imagemagick support is compiled into emacs."
+Has only an effect if `doc-view-scale-internally' is non-nil and support for
+scaling is compiled into emacs."
   :version "24.1"
   :type 'number
   :group 'doc-view)
@@ -251,20 +284,23 @@ of the page moves to the previous page."
 ;;;; Internal Variables
 
 (defun doc-view-new-window-function (winprops)
+  ;; (message "New window %s for buf %s" (car winprops) (current-buffer))
+  (cl-assert (or (eq t (car winprops))
+                 (eq (window-buffer (car winprops)) (current-buffer))))
   (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
         (progn
-          (assert (eq (overlay-buffer ol) (current-buffer)))
-          (setq ol (copy-overlay ol)))
-      (assert (not (get-char-property (point-min) 'display)))
+          (setq ol (copy-overlay ol))
+          ;; `ol' might actually be dead.
+          (move-overlay ol (point-min) (point-max)))
       (setq ol (make-overlay (point-min) (point-max) nil t))
       (overlay-put ol 'doc-view t))
     (overlay-put ol 'window (car winprops))
+    (unless (windowp (car winprops))
+      ;; It's a pseudo entry.  Let's make sure it's not displayed (the
+      ;; `window' property is only effective if its value is a window).
+      (cl-assert (eq t (car winprops)))
+      (delete-overlay ol))
     (image-mode-window-put 'overlay ol winprops)))
 
 (defvar doc-view-current-files nil
@@ -305,6 +341,19 @@ the (uncompressed, extracted) file residing in
   "The type of document in the current buffer.
 Can be `dvi', `pdf', or `ps'.")
 
+(defvar doc-view-single-page-converter-function nil
+  "Function to call to convert a single page of the document to a bitmap file.
+May operate on the source document or on some intermediate (typically PDF)
+conversion of it.")
+
+(defvar-local doc-view--image-type nil
+  "The type of image in the current buffer.
+Can be `png' or `tiff'.")
+
+(defvar-local doc-view--image-file-pattern nil
+  "The `format' pattern of image file names.
+Typically \"page-%s.png\".")
+
 ;;;; DocView Keymaps
 
 (defvar doc-view-mode-map
@@ -340,6 +389,7 @@ Can be `dvi', `pdf', or `ps'.")
     ;; Slicing the image
     (define-key map (kbd "s s")       'doc-view-set-slice)
     (define-key map (kbd "s m")       'doc-view-set-slice-using-mouse)
+    (define-key map (kbd "s b")       'doc-view-set-slice-from-bounding-box)
     (define-key map (kbd "s r")       'doc-view-reset-slice)
     ;; Searching
     (define-key map (kbd "C-s")       'doc-view-search)
@@ -381,6 +431,7 @@ Can be `dvi', `pdf', or `ps'.")
      )
     "---"
     ["Set Slice"               doc-view-set-slice-using-mouse]
+    ["Set Slice (BoundingBox)"  doc-view-set-slice-from-bounding-box]
     ["Set Slice (manual)"      doc-view-set-slice]
     ["Reset Slice"             doc-view-reset-slice]
     "---"
@@ -441,24 +492,26 @@ Can be `dvi', `pdf', or `ps'.")
     ;; 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))))
+    (let ((file (expand-file-name
+                 (format doc-view--image-file-pattern 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
-                             (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))))))))
+        (funcall doc-view-single-page-converter-function
+                doc-view-buffer-file-name file page
+                (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))))))))
     (overlay-put (doc-view-current-overlay)
                  'help-echo (doc-view-current-info))))
 
@@ -554,7 +607,8 @@ at the top edge of the page moves to the previous page."
   "Kill the current converter process(es)."
   (interactive)
   (while (consp doc-view-current-converter-processes)
-    (ignore-errors ;; Maybe it's dead already?
+    (ignore-errors ;; Some entries might not be processes, and maybe
+                  ;; some are dead already?
       (kill-process (pop doc-view-current-converter-processes))))
   (when doc-view-current-timer
     (cancel-timer doc-view-current-timer)
@@ -641,14 +695,16 @@ OpenDocument format)."
                       (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))
+       ((memq type '(postscript ps eps pdf))
+        ;; FIXME: allow mupdf here
         (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)))
+       ((eq type 'djvu)
+        (executable-find "ddjvu"))
        (t ;; unknown image type
         nil))))
 
@@ -657,19 +713,22 @@ OpenDocument format)."
 (defvar doc-view-shrink-factor 1.125)
 
 (defun doc-view-enlarge (factor)
-  "Enlarge the document."
+  "Enlarge the document by FACTOR."
   (interactive (list doc-view-shrink-factor))
-  (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)))
+  (if (and doc-view-scale-internally
+           (eq (plist-get (cdr (doc-view-current-image)) :type)
+               'imagemagick))
+      ;; ImageMagick supports on-the-fly-rescaling.
+      (let ((new (ceiling (* factor doc-view-image-width))))
+        (unless (equal new doc-view-image-width)
+          (setq-local doc-view-image-width new)
+          (doc-view-insert-image
+           (plist-get (cdr (doc-view-current-image)) :file)
+           :width doc-view-image-width)))
+    (let ((new (ceiling (* factor doc-view-resolution))))
+      (unless (equal new doc-view-resolution)
+        (setq-local doc-view-resolution new)
+        (doc-view-reconvert-doc)))))
 
 (defun doc-view-shrink (factor)
   "Shrink the document."
@@ -737,12 +796,14 @@ min {(window-width / image-width), (window-height / image-height)} times."
               (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)))))
+                                 (/ (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))))
+                                (/ (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)
@@ -756,6 +817,7 @@ Should be invoked when the cached images aren't up-to-date."
   ;; Clear the old cached files
   (when (file-exists-p (doc-view-current-cache-dir))
     (delete-directory (doc-view-current-cache-dir) 'recursive))
+  (kill-local-variable 'doc-view-last-page-number)
   (doc-view-initiate-display))
 
 (defun doc-view-sentinel (proc event)
@@ -802,6 +864,45 @@ Should be invoked when the cached images aren't up-to-date."
                            (list "-o" pdf dvi)
                            callback)))
 
+(defun doc-view-pdf->png-converter-ghostscript (pdf png page callback)
+  (doc-view-start-process
+   "pdf/ps->png" doc-view-ghostscript-program
+   `(,@doc-view-ghostscript-options
+     ,(format "-r%d" (round doc-view-resolution))
+     ,@(if page `(,(format "-dFirstPage=%d" page)))
+     ,@(if page `(,(format "-dLastPage=%d" page)))
+     ,(concat "-sOutputFile=" png)
+     ,pdf)
+   callback))
+
+(defalias 'doc-view-ps->png-converter-ghostscript
+  'doc-view-pdf->png-converter-ghostscript)
+
+(defun doc-view-djvu->tiff-converter-ddjvu (djvu tiff page callback)
+  "Convert PAGE of a DJVU file to bitmap(s) asynchronously.
+Call CALLBACK with no arguments when done.
+If PAGE is nil, convert the whole document."
+  (doc-view-start-process
+   "djvu->tiff" "ddjvu"
+   `("-format=tiff"
+     ;; ddjvu only accepts the range 1-999.
+     ,(format "-scale=%d" (round doc-view-resolution))
+     ;; -eachpage was only added after djvulibre-3.5.25.3!
+     ,@(unless page '("-eachpage"))
+     ,@(if page `(,(format "-page=%d" page)))
+     ,djvu
+     ,tiff)
+   callback))
+
+(defun doc-view-pdf->png-converter-mupdf (pdf png page callback)
+  (doc-view-start-process
+   "pdf->png" doc-view-pdfdraw-program
+   `(,(concat "-o" png)
+     ,(format "-r%d" (round doc-view-resolution))
+     ,pdf
+     ,@(if page `(,(format "%d" page))))
+   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
@@ -811,13 +912,14 @@ is named like ODF with the extension turned to pdf."
                          callback))
 
 (defun doc-view-pdf/ps->png (pdf-ps png)
+  ;; FIXME: Fix name and docstring to account for djvu&tiff.
   "Convert PDF-PS to PNG asynchronously."
-  (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))
+  (funcall
+   (pcase doc-view-doc-type
+     (`pdf doc-view-pdf->png-converter-function)
+     (`djvu #'doc-view-djvu->tiff-converter-ddjvu)
+     (_ #'doc-view-ps->png-converter-ghostscript))
+   pdf-ps png nil
    (let ((resolution doc-view-resolution))
      (lambda ()
        ;; Only create the resolution file when it's all done, so it also
@@ -830,6 +932,7 @@ is named like ODF with the extension turned to pdf."
          (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
@@ -837,25 +940,10 @@ is named like ODF with the extension turned to pdf."
                        '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.
+(defun doc-view-document->bitmap (pdf png pages)
+  "Convert a document file to bitmap images asynchronously.
 Start by converting PAGES, and then the rest."
   (if (null pages)
       (doc-view-pdf/ps->png pdf png)
@@ -864,11 +952,11 @@ Start by converting PAGES, and then the rest."
     ;; a single page anyway, and of the remaining 1%, few cases will have
     ;; consecutive pages, it's not worth the trouble.
     (let ((rest (cdr pages)))
-      (doc-view-pdf->png-1
-       pdf (format png (car pages)) (car pages)
+      (funcall doc-view-single-page-converter-function
+              pdf (format png (car pages)) (car pages)
        (lambda ()
          (if rest
-             (doc-view-pdf->png pdf png rest)
+             (doc-view-document->bitmap 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
@@ -876,8 +964,8 @@ Start by converting PAGES, and then the rest."
            ;; 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)))))
+              (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)))))))
 
@@ -889,33 +977,33 @@ Start by converting PAGES, and then the rest."
                           (list "-raw" pdf txt)
                           callback))
 
+(defun doc-view-current-cache-doc-pdf ()
+  "Return the name of the doc.pdf in the current cache dir.
+  This file exists only if the current document isn't a PDF or PS file already."
+  (expand-file-name "doc.pdf" (doc-view-current-cache-dir)))
+
 (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
+  (pcase 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
+    (`ps
      ;; Doc is a PS, so convert it to PDF (which will be converted to
      ;; TXT thereafter).
-     (let ((pdf (expand-file-name "doc.pdf"
-                                 (doc-view-current-cache-dir))))
+     (let ((pdf (doc-view-current-cache-doc-pdf)))
        (doc-view-ps->pdf doc-view-buffer-file-name pdf
                          (lambda () (doc-view-pdf->txt pdf txt callback)))))
-    (dvi
+    (`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))
-    (odf
+     (doc-view-pdf->txt (doc-view-current-cache-doc-pdf) 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"))))
+     (doc-view-pdf->txt (doc-view-current-cache-doc-pdf) txt callback))
+    (_ (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."
@@ -947,27 +1035,27 @@ Those files are saved in the directory given by the function
   ;; preserves the horizontal/vertical scroll settings (which are otherwise
   ;; 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))))
+  (let ((png-file (expand-file-name
+                   (format doc-view--image-file-pattern "%d")
+                   (doc-view-current-cache-dir))))
     (make-directory (doc-view-current-cache-dir) t)
-    (case doc-view-doc-type
-      (dvi
+    (pcase 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)))
+       (let ((pdf (doc-view-current-cache-doc-pdf)))
          (doc-view-dvi->pdf doc-view-buffer-file-name pdf
                             (lambda () (doc-view-pdf/ps->png pdf png-file)))))
-      (odf
+      (`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))
-        ;; The unoconv tool only supports a output directory, but no
+       (let ((pdf (doc-view-current-cache-doc-pdf))
+             (opdf (expand-file-name
+                    (concat (file-name-base doc-view-buffer-file-name)
+                            ".pdf")
+                    doc-view-current-cache-dir))
+             (png-file png-file))
+        ;; The unoconv tool only supports an 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
@@ -975,11 +1063,11 @@ Those files are saved in the directory given by the function
                              ;; Rename to doc.pdf
                              (rename-file opdf pdf)
                              (doc-view-pdf/ps->png pdf png-file)))))
-      (pdf
+      ((or `pdf `djvu)
        (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 doc to bitmap images starting with the active pages.
+         (doc-view-document->bitmap doc-view-buffer-file-name png-file pages)))
+      (_
        ;; Convert to PNG images.
        (doc-view-pdf/ps->png doc-view-buffer-file-name png-file)))))
 
@@ -993,8 +1081,9 @@ You can use this function to tell doc-view not to display the
 margins of the document.  It prompts for the top-left corner (X
 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'."
+See `doc-view-set-slice-using-mouse' and
+`doc-view-set-slice-from-bounding-box' for more convenient ways
+to do that.  To reset the slice use `doc-view-reset-slice'."
   (interactive
    (let* ((size (image-size (doc-view-current-image) t))
          (a (read-number (format "Top-left X (0..%d): " (car size))))
@@ -1025,6 +1114,83 @@ dragging it to its bottom-right corner.  See also
          (setq done t))))
     (doc-view-set-slice x y w h)))
 
+(defun doc-view-get-bounding-box ()
+  "Get the BoundingBox information of the current page."
+  (let* ((page (doc-view-current-page))
+        (doc (let ((cache-doc (doc-view-current-cache-doc-pdf)))
+               (if (file-exists-p cache-doc)
+                   cache-doc
+                 doc-view-buffer-file-name)))
+        (o (shell-command-to-string
+            (concat doc-view-ghostscript-program
+                    " -dSAFER -dBATCH -dNOPAUSE -q -sDEVICE=bbox "
+                    (format "-dFirstPage=%s -dLastPage=%s %s"
+                            page page doc)))))
+    (save-match-data
+      (when (string-match (concat "%%BoundingBox: "
+                                 "\\([[:digit:]]+\\) \\([[:digit:]]+\\) "
+                                 "\\([[:digit:]]+\\) \\([[:digit:]]+\\)") o)
+       (mapcar #'string-to-number
+               (list (match-string 1 o)
+                     (match-string 2 o)
+                     (match-string 3 o)
+                     (match-string 4 o)))))))
+
+(defvar doc-view-paper-sizes
+  '((a4 595 842)
+    (a4-landscape 842 595)
+    (letter 612 792)
+    (letter-landscape 792 612)
+    (legal 612 1008)
+    (legal-landscape 1008 612)
+    (a3 842 1191)
+    (a3-landscape 1191 842)
+    (tabloid 792 1224)
+    (ledger 1224 792))
+  "An alist from paper size names to dimensions.")
+
+(defun doc-view-guess-paper-size (iw ih)
+  "Guess the paper size according to the aspect ratio."
+  (cl-labels ((div (x y)
+                  (round (/ (* 100.0 x) y))))
+    (let ((ar (div iw ih))
+         (al (mapcar (lambda (l)
+                       (list (div (nth 1 l) (nth 2 l)) (car l)))
+                     doc-view-paper-sizes)))
+      (cadr (assoc ar al)))))
+
+(defun doc-view-scale-bounding-box (ps iw ih bb)
+  (list (/ (* (nth 0 bb) iw) (nth 1 (assoc ps doc-view-paper-sizes)))
+       (/ (* (nth 1 bb) ih) (nth 2 (assoc ps doc-view-paper-sizes)))
+       (/ (* (nth 2 bb) iw) (nth 1 (assoc ps doc-view-paper-sizes)))
+       (/ (* (nth 3 bb) ih) (nth 2 (assoc ps doc-view-paper-sizes)))))
+
+(defun doc-view-set-slice-from-bounding-box (&optional force-paper-size)
+  "Set the slice from the document's BoundingBox information.
+The result is that the margins are almost completely cropped,
+much more accurate than could be done manually using
+`doc-view-set-slice-using-mouse'."
+  (interactive "P")
+  (let ((bb (doc-view-get-bounding-box)))
+    (if (not bb)
+       (message "BoundingBox couldn't be determined")
+      (let* ((is (image-size (doc-view-current-image) t))
+            (iw (car is))
+            (ih (cdr is))
+            (ps (or (and (null force-paper-size)
+                          (doc-view-guess-paper-size iw ih))
+                    (intern (completing-read "Paper size: "
+                                              doc-view-paper-sizes
+                                             nil t))))
+            (bb (doc-view-scale-bounding-box ps iw ih bb))
+            (x1 (nth 0 bb))
+            (y1 (nth 1 bb))
+            (x2 (nth 2 bb))
+            (y2 (nth 3 bb)))
+       ;; We keep a 2 pixel margin.
+       (doc-view-set-slice (- x1 2) (- ih y2 2)
+                           (+ (- x2 x1) 4) (+ (- y2 y1) 4))))))
+
 (defun doc-view-reset-slice ()
   "Reset the current slice.
 After calling this function whole pages will be visible again."
@@ -1043,10 +1209,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))
-                  (if (not (fboundp 'imagemagick-types))
-                      (apply 'create-image file 'png nil args)
+                  (if (not (and doc-view-scale-internally
+                                 (fboundp 'imagemagick-types)))
+                      (apply 'create-image file doc-view--image-type nil args)
                     (unless (member :width args)
-                      (setq args (append args (list :width doc-view-image-width))))
+                      (setq args `(,@args :width ,doc-view-image-width)))
                     (apply 'create-image file 'imagemagick nil args))))
         (slice (doc-view-current-slice)))
     (setf (doc-view-current-image) image)
@@ -1094,19 +1261,26 @@ have the page we want to view."
     (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)
+                                   (format doc-view--image-file-pattern
+                                           "[0-9]+")
+                                   t)
                   'doc-view-sort))
+      (unless (eq (length prev-pages) (length doc-view-current-files))
+       (force-mode-line-update))
       (dolist (win (or (get-buffer-window-list buffer nil t)
-                      (list (selected-window))))
+                      (list t)))
        (let* ((page (doc-view-current-page win))
-              (pagefile (expand-file-name (format "page-%d.png" page)
-                                          (doc-view-current-cache-dir))))
+              (pagefile (expand-file-name
+                          (format doc-view--image-file-pattern 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))))))))
+           (if (windowp win)
+               (with-selected-window win
+                 (cl-assert (eq (current-buffer) buffer) t)
+                 (doc-view-goto-page page))
+             (doc-view-goto-page page))))))))
 
 (defun doc-view-buffer-message ()
   ;; Only show this message initially, not when refreshing the buffer (in which
@@ -1150,6 +1324,10 @@ For now these keys are useful:
 
 ;;;;; Toggle between editing and viewing
 
+(defvar-local doc-view-saved-settings nil
+  "Doc-view settings saved while in some other mode.")
+(put 'doc-view-saved-settings 'permanent-local t)
+
 (defun doc-view-toggle-display ()
   "Toggle between editing a document as text or viewing it."
   (interactive)
@@ -1159,7 +1337,7 @@ For now these keys are useful:
        (doc-view-kill-proc)
        (setq buffer-read-only nil)
        (remove-overlays (point-min) (point-max) 'doc-view t)
-       (set (make-local-variable 'image-mode-winprops-alist) t)
+       (setq-local image-mode-winprops-alist t)
        ;; Switch to the previously used major mode or fall back to
        ;; normal mode.
        (doc-view-fallback-mode)
@@ -1287,12 +1465,13 @@ If BACKWARD is non-nil, jump to the previous match."
        ;; 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\\'"))
+       (> (length (directory-files
+                   (doc-view-current-cache-dir)
+                   nil (format doc-view--image-file-pattern "[0-9]+")))
           0)))
 
 (defun doc-view-initiate-display ()
-  ;; Switch to image display if possible
+  ;; Switch to image display if possible.
   (if (doc-view-mode-p doc-view-doc-type)
       (progn
        (doc-view-buffer-message)
@@ -1300,7 +1479,7 @@ If BACKWARD is non-nil, jump to the previous match."
        (if (doc-view-already-converted-p)
            (progn
              (message "DocView: using cached files!")
-             ;; Load the saved resolution
+             ;; Load the saved resolution.
              (let* ((res-file (expand-file-name "resolution.el"
                                                  (doc-view-current-cache-dir)))
                      (res
@@ -1309,7 +1488,7 @@ If BACKWARD is non-nil, jump to the previous match."
                           (insert-file-contents res-file)
                           (read (current-buffer))))))
                 (when (numberp res)
-                 (set (make-local-variable 'doc-view-resolution) res)))
+                 (setq-local doc-view-resolution res)))
              (doc-view-display (current-buffer) 'force))
          (doc-view-convert-current-doc))
        (message
@@ -1361,6 +1540,8 @@ If BACKWARD is non-nil, jump to the previous match."
                         ("pdf" pdf) ("epdf" pdf)
                         ;; PostScript
                         ("ps" ps) ("eps" ps)
+                        ;; DjVu
+                        ("djvu" djvu)
                         ;; OpenDocument formats
                         ("odt" odf) ("ods" odf) ("odp" odf) ("odg" odf)
                         ("odc" odf) ("odi" odf) ("odm" odf) ("ott" odf)
@@ -1375,14 +1556,25 @@ If BACKWARD is non-nil, jump to the previous match."
           (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"))))))
+           ((looking-at "\367\002") '(dvi))
+           ((looking-at "AT&TFORM") '(djvu))))))
+    (setq-local 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"))))))
+
+(defun doc-view-set-up-single-converter ()
+  "Find the right single-page converter for the current document type"
+  (pcase-let ((`(,conv-function ,type ,extension)
+               (pcase doc-view-doc-type
+                 (`djvu (list #'doc-view-djvu->tiff-converter-ddjvu 'tiff "tif"))
+                 (_     (list doc-view-pdf->png-converter-function  'png  "png")))))
+    (setq-local doc-view-single-page-converter-function conv-function)
+    (setq-local doc-view--image-type type)
+    (setq-local doc-view--image-file-pattern (concat "page-%s." extension))))
 
 ;;;###autoload
 (defun doc-view-mode ()
@@ -1402,21 +1594,24 @@ toggle between displaying the document or editing it as text.
       ;; returns nil for tar members.
       (doc-view-fallback-mode)
 
-    (let* ((prev-major-mode (if (eq major-mode 'doc-view-mode)
+    (let* ((prev-major-mode (if (derived-mode-p 'doc-view-mode)
                                doc-view-previous-major-mode
-                             (when (not (memq major-mode
-                                              '(doc-view-mode fundamental-mode)))
+                             (unless (eq major-mode 'fundamental-mode)
                                major-mode))))
       (kill-all-local-variables)
-      (set (make-local-variable 'doc-view-previous-major-mode) prev-major-mode))
+      (setq-local doc-view-previous-major-mode prev-major-mode))
+
+    (dolist (var doc-view-saved-settings)
+      (set (make-local-variable (car var)) (cdr var)))
 
     ;; Figure out the document type.
     (unless doc-view-doc-type
       (doc-view-set-doc-type))
+    (doc-view-set-up-single-converter)
 
     (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)
+    (setq-local doc-view-buffer-file-name
         (cond
          (jka-compr-really-do-compress
            ;; FIXME: there's a risk of name conflicts here.
@@ -1455,20 +1650,19 @@ toggle between displaying the document or editing it as text.
              '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 (doc-view-last-page-number)))))
+    (setq-local mode-line-position
+                '(" P" (:eval (number-to-string (doc-view-current-page)))
+                  "/" (: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)
+    (setq-local auto-hscroll-mode nil)
+    (setq-local mwheel-scroll-up-function #'doc-view-scroll-up-or-next-page)
+    (setq-local mwheel-scroll-down-function
+                #'doc-view-scroll-down-or-previous-page)
+    (setq-local 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)
+    (add-hook 'after-revert-hook 'doc-view-reconvert-doc nil t)
+    (setq-local bookmark-make-record-function
+                #'doc-view-bookmark-make-record)
     (setq mode-name "DocView"
          buffer-read-only t
          major-mode 'doc-view-mode)
@@ -1477,18 +1671,25 @@ toggle between displaying the document or editing it as text.
     ;; 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)
+    (setq-local 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))))
+  (let ((vars (if (derived-mode-p 'doc-view-mode)
+                  (mapcar (lambda (var) (cons var (symbol-value var)))
+                          '(doc-view-resolution
+                            image-mode-winprops-alist)))))
+    (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)))
+    (when vars
+      (setq-local doc-view-saved-settings vars))))
 
 ;;;###autoload
 (defun doc-view-mode-maybe ()