]> code.delx.au - gnu-emacs/blobdiff - lisp/doc-view.el
Backported revisions 2012-12-29T12:33:33Z!fgallina@gnu.org and 2012-12-29T12:57:49Z...
[gnu-emacs] / lisp / doc-view.el
index 7bead624cc7683114c5f6b17649ccf1d3377a987..f8975a57b7b7371faf93c5dc4656b2a0a78ea2b3 100644 (file)
@@ -1,10 +1,10 @@
 ;;; doc-view.el --- View PDF/PostScript/DVI files in Emacs -*- lexical-binding: t -*-
 
 
-;; Copyright (C) 2007-2011 Free Software Foundation, Inc.
+;; Copyright (C) 2007-2012 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).
 ;; (except the tooltip) if the next match is on the same page.
 
 ;; And it's much slower than the current search facility, because
-;; isearch really searches for each step forward or backward wheras
+;; isearch really searches for each step forward or backward whereas
 ;; the current approach searches once and then it knows to which
 ;; pages to jump.
 
 
 ;;; Code:
 
-(eval-when-compile (require 'cl))
+(eval-when-compile (require 'cl-lib))
 (require 'dired)
 (require 'image-mode)
 (require 'jka-compr)
   :group 'multimedia
   :prefix "doc-view-")
 
-(defcustom doc-view-ghostscript-program (executable-find "gs")
+(defcustom doc-view-ghostscript-program "gs"
   "Program to convert PS and PDF files to PNG."
   :type 'file
   :group 'doc-view)
@@ -172,10 +176,11 @@ Higher values result in larger images."
 (defcustom doc-view-image-width 850
   "Default image width.
 Has only an effect if imagemagick support is compiled into emacs."
+  :version "24.1"
   :type 'number
   :group 'doc-view)
 
-(defcustom doc-view-dvipdfm-program (executable-find "dvipdfm")
+(defcustom doc-view-dvipdfm-program "dvipdfm"
   "Program to convert DVI files to PDF.
 
 DVI file will be converted to PDF before the resulting PDF is
@@ -186,7 +191,7 @@ If this and `doc-view-dvipdf-program' are set,
   :type 'file
   :group 'doc-view)
 
-(defcustom doc-view-dvipdf-program (executable-find "dvipdf")
+(defcustom doc-view-dvipdf-program "dvipdf"
   "Program to convert DVI files to PDF.
 
 DVI file will be converted to PDF before the resulting PDF is
@@ -197,21 +202,22 @@ If this and `doc-view-dvipdfm-program' are set,
   :type 'file
   :group 'doc-view)
 
-(defcustom doc-view-unoconv-program (executable-find "unoconv")
+(defcustom doc-view-unoconv-program "unoconv"
   "Program to convert any file type readable by OpenOffice.org to PDF.
 
 Needed for viewing OpenOffice.org (and MS Office) files."
+  :version "24.1"
   :type 'file
   :group 'doc-view)
 
-(defcustom doc-view-ps2pdf-program (executable-find "ps2pdf")
+(defcustom doc-view-ps2pdf-program "ps2pdf"
   "Program to convert PS files to PDF.
 
 PS files will be converted to PDF before searching is possible."
   :type 'file
   :group 'doc-view)
 
-(defcustom doc-view-pdftotext-program (executable-find "pdftotext")
+(defcustom doc-view-pdftotext-program "pdftotext"
   "Program to convert PDF files to plain text.
 
 Needed for searching."
@@ -249,20 +255,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
@@ -328,12 +337,17 @@ 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)
     ;; 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)
@@ -375,6 +389,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]
     "---"
@@ -548,7 +563,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)
@@ -565,18 +581,18 @@ at the top edge of the page moves to the previous page."
 (defun doc-view-make-safe-dir (dir)
   (condition-case nil
       (let ((umask (default-file-modes)))
-        (unwind-protect
-            (progn
-              ;; Create temp files with strict access rights.  It's easy to
-              ;; loosen them later, whereas it's impossible to close the
-              ;; time-window of loose permissions otherwise.
-              (set-default-file-modes #o0700)
-              (make-directory dir))
-          ;; Reset the umask.
-          (set-default-file-modes umask)))
+       (unwind-protect
+           (progn
+             ;; Create temp files with strict access rights.  It's easy to
+             ;; loosen them later, whereas it's impossible to close the
+             ;; time-window of loose permissions otherwise.
+             (set-default-file-modes #o0700)
+             (make-directory dir))
+         ;; Reset the umask.
+         (set-default-file-modes umask)))
     (file-already-exists
-     (if (file-symlink-p dir)
-         (error "Danger: %s points to a symbolic link" dir))
+     (when (file-symlink-p dir)
+       (error "Danger: %s points to a symbolic link" dir))
      ;; In case it was created earlier with looser rights.
      ;; We could check the mode info returned by file-attributes, but it's
      ;; a pain to parse and it may not tell you what we want under
@@ -585,7 +601,12 @@ at the top edge of the page moves to the previous page."
      ;; This also ends up checking a bunch of useful conditions: it makes
      ;; sure we have write-access to the directory and that we own it, thus
      ;; closing a bunch of security holes.
-     (set-file-modes dir #o0700))))
+     (condition-case error
+        (set-file-modes dir #o0700)
+       (file-error
+       (error
+        (format "Unable to use temporary directory %s: %s"
+                dir (mapconcat 'identity (cdr error) " "))))))))
 
 (defun doc-view-current-cache-dir ()
   "Return the directory where the png files of the current doc should be saved.
@@ -610,9 +631,10 @@ It's a subdirectory of `doc-view-cache-directory'."
 (defun doc-view-remove-if (predicate list)
   "Return LIST with all items removed that satisfy PREDICATE."
   (let (new-list)
-    (dolist (item list (nreverse new-list))
+    (dolist (item list)
       (when (not (funcall predicate item))
-       (setq new-list (cons item new-list))))))
+       (setq new-list (cons item new-list))))
+     (nreverse new-list)))
 
 ;;;###autoload
 (defun doc-view-mode-p (type)
@@ -645,25 +667,101 @@ 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)))
+      ;; ImageMagick supports on-the-fly-rescaling.
+      (let ((new (ceiling (* factor doc-view-image-width))))
+        (unless (equal new doc-view-image-width)
+          (set (make-local-variable '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)
+        (set (make-local-variable 'doc-view-resolution) new)
+        (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."
@@ -672,6 +770,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)
@@ -799,43 +898,43 @@ Start by converting PAGES, and then the rest."
 
 (defun doc-view-pdf->txt (pdf txt callback)
   "Convert PDF to TXT asynchronously and call CALLBACK when finished."
-  (or doc-view-pdftotext-program
+  (or (executable-find doc-view-pdftotext-program)
       (error "You need the `pdftotext' program to convert a PDF to text"))
   (doc-view-start-process "pdf->txt" doc-view-pdftotext-program
                           (list "-raw" pdf txt)
                           callback))
 
+(defun doc-view-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."
-  (or doc-view-ps2pdf-program
+  (or (executable-find doc-view-ps2pdf-program)
       (error "You need the `ps2pdf' program to convert PS to PDF"))
   (doc-view-start-process "ps->pdf" doc-view-ps2pdf-program
                           (list
@@ -866,23 +965,21 @@ Those files are saved in the directory given by the function
   (let ((png-file (expand-file-name "page-%d.png"
                                     (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))
+       (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 a output directory, but no
         ;; file name.  It's named like the input file with the
         ;; extension replaced by pdf.
@@ -891,11 +988,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
+      (`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)))))
 
@@ -909,8 +1006,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))))
@@ -941,6 +1039,82 @@ 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: "
+                                             (mapcar #'car 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."
@@ -1013,16 +1187,18 @@ have the page we want to view."
                                    "page-[0-9]+\\.png" t)
                   'doc-view-sort))
       (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))))
          (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
@@ -1066,6 +1242,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)
@@ -1318,13 +1498,16 @@ 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))
+      (set (make-local-variable '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
@@ -1398,13 +1581,20 @@ toggle between displaying the document or editing it as text.
 
 (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 ()
@@ -1420,8 +1610,11 @@ to the next best mode."
 
 ;;;###autoload
 (define-minor-mode doc-view-minor-mode
-  "Toggle Doc view minor mode.
-With arg, turn Doc view minor mode on if arg is positive, off otherwise.
+  "Toggle displaying buffer via Doc View (Doc View minor mode).
+With a prefix argument ARG, enable Doc View minor mode if ARG is
+positive, and disable it otherwise.  If called from Lisp, enable
+the mode if ARG is omitted or nil.
+
 See the command `doc-view-mode' for more information on this mode."
   nil " DocView" doc-view-minor-mode-map
   :group 'doc-view
@@ -1473,7 +1666,7 @@ See the command `doc-view-mode' for more information on this mode."
 (provide 'doc-view)
 
 ;; Local Variables:
-;; mode: outline-minor
+;; eval: (outline-minor-mode 1)
 ;; End:
 
 ;;; doc-view.el ends here