Question

I'm trying to use Document View in Emacs to read PDFs, but I can't figure out how to make it behave similarly to the 'fit to width' command many PDF readers have. Is there an internal way to do this?

Was it helpful?

Solution

The following snippet defines a new minor-mode doc-view-autofit-mode, which I have activated below using doc-view-mode-hook. It works for me on Emacs 24.3 on Ubuntu 14.04, even to the point of resizing the zoom when I resize the window!

(There is usually a short resize delay thanks to doc-view-autofit-timer-start, but I'm happy to live with this.)

I take no credit for the solution; I found this code on the emacs-devel mailing list.

(require 'cl)

;;;; Automatic fitting minor mode
(defcustom doc-view-autofit-timer-start 1.0
  "Initial value (seconds) for the timer that delays the fitting when
`doc-view-autofit-fit' is called (Which is when a window
configuration change occurs and a document needs to be fitted)."
  :type 'number
  :group 'doc-view)

(defcustom doc-view-autofit-timer-inc 0.02
  "Value to increase (seconds) the timer (see `doc-view-autofit-timer-start')
by, if there is another window configuration change occuring, before
it runs out."
  :type 'number
  :group 'doc-view)

(defcustom doc-view-autofit-default-fit 'width
  "The fitting type initially used when mode is enabled.
Valid values are: width, height, page."
  :type 'symbol
  :group 'doc-view)

(defvar doc-view-autofit-mode-map
  (let ((map (make-sparse-keymap)))
    (define-key map (kbd "C-c W") 'doc-view-autofit-width)
    (define-key map (kbd "C-c H") 'doc-view-autofit-height)
    (define-key map (kbd "C-c P") 'doc-view-autofit-page)
    map)
  "Keymap used by `doc-view-autofit-mode'.")

(defun doc-view-autofit-set (type)
  "Set autofitting to TYPE for current buffer."
  (when doc-view-autofit-mode
    (setq doc-view-autofit-type type)
    (doc-view-autofit-fit)))

(defun doc-view-autofit-width ()
  "Set autofitting to width for current buffer."
  (interactive) (doc-view-autofit-set 'width))

(defun doc-view-autofit-height ()
  "Set autofitting to height for current buffer."
  (interactive) (doc-view-autofit-set 'height))

(defun doc-view-autofit-page ()
  "Set autofitting to page for current buffer."
  (interactive) (doc-view-autofit-set 'page))

(defun doc-view-autofit-fit ()
  "Fits the document in the selected window's buffer
delayed with a timer, so multiple calls in succession
don't cause as much overhead."
  (lexical-let
      ((window (selected-window)))
    (if (equal doc-view-autofit-timer nil)
        (setq doc-view-autofit-timer
              (run-with-timer
               doc-view-autofit-timer-start nil
               (lambda ()
                 (if (window-live-p window)
                     (save-selected-window
                       (select-window window)
                       (cancel-timer doc-view-autofit-timer)
                       (setq doc-view-autofit-timer nil)
                       (cond
                        ((equal 'width doc-view-autofit-type)
                         (doc-view-fit-width-to-window))
                        ((equal 'height doc-view-autofit-type)
                         (doc-view-fit-height-to-window))
                        ((equal 'page doc-view-autofit-type)
                         (doc-view-fit-page-to-window))))))))
      (timer-inc-time doc-view-autofit-timer doc-view-autofit-timer-inc))))

(define-minor-mode doc-view-autofit-mode
  "Minor mode for automatic (timer based) fitting in DocView."
  :lighter " AFit" :keymap doc-view-autofit-mode-map :group 'doc-view
  (when doc-view-autofit-mode
    (set (make-local-variable 'doc-view-autofit-type)
         doc-view-autofit-default-fit)
    (set (make-local-variable 'doc-view-autofit-timer) nil)
    (add-hook 'window-configuration-change-hook
              'doc-view-autofit-fit nil t)
    (doc-view-autofit-fit))
  (when (not doc-view-autofit-mode)
    (remove-hook 'window-configuration-change-hook
                 'doc-view-autofit-fit t)
    (when doc-view-autofit-timer
      (cancel-timer doc-view-autofit-timer)
      (setq doc-view-autofit-timer nil))
    (setq doc-view-autofit-type nil)))

(add-hook 'doc-view-mode-hook 'doc-view-autofit-mode)

OTHER TIPS

It works for me:

(add-hook 'doc-view-mode-hook 'doc-view-fit-width-to-window)

Update: It doesn't work correctly if a conversion (to png or something else) is still ongoing (First opening the document). There is alternative, more reliable way, which handles this special case (it doesn't use hook at all but uses advice):

(defadvice doc-view-display (after fit-width activate)
  (doc-view-fit-width-to-window))

The following is a slight modification of the answer by Chris -- it provides compatibility with functions like find-file-other-window -- e.g., when the selected-window is different than the one displaying the *.pdf file.

(defvar last-displayed-doc-view-buffer nil)

(defun get-last-displayed-doc-view-buffer ()
  (setq last-displayed-doc-view-buffer (current-buffer)))

(add-hook 'doc-view-mode-hook 'get-last-displayed-doc-view-buffer)

(defun doc-view-autofit-fit ()
  "Fits the document in the selected window's buffer
delayed with a timer, so multiple calls in succession
don't cause as much overhead."
  (if (null doc-view-autofit-timer)
    (setq doc-view-autofit-timer
      (run-with-timer doc-view-autofit-timer-start nil (lambda ()
        (let* (
            (selected-window
              (cond
                ((eq major-mode 'doc-view-mode)
                  (selected-window))
                (t
                  (get-buffer-window last-displayed-doc-view-buffer))))
            (current-buffer
              (cond
                ((eq major-mode 'doc-view-mode)
                  (current-buffer))
                (t
                  (get-buffer last-displayed-doc-view-buffer))))
            (selected-fit
              (when (buffer-live-p (get-buffer current-buffer))
                (with-current-buffer (get-buffer current-buffer)
                  doc-view-autofit-type))) )
          (when (window-live-p selected-window)
            (with-selected-window selected-window
              (when doc-view-autofit-timer (cancel-timer doc-view-autofit-timer))
              (setq doc-view-autofit-timer nil)
              (cond
                ((eq 'width selected-fit)
                  (doc-view-fit-width-to-window))
                ((eq 'height selected-fit)
                  (doc-view-fit-height-to-window))
                ((eq 'page selected-fit)
                  (doc-view-fit-page-to-window)))))))))
    (timer-inc-time doc-view-autofit-timer doc-view-autofit-timer-inc)))

And, as noted in my earlier comment to Chris' answer, the following variables need definitions:

(defvar doc-view-autofit-timer nil)

(defvar doc-view-autofit-type nil)

Because the modification above adds a new function to the doc-view-mode-hook to obtain the current-buffer, which is needed for the function doc-view-autofit-fit, it is necessary to ensure that the latter function is appended to the end of the doc-view-mode-hook. So the change looks like this -- i.e., we add a t for the append argument:

(add-hook 'doc-view-mode-hook 'doc-view-autofit-mode t)

Everything else from Chris's answer that has not been superseded by the above modifications remain in effect.


TO DO:

  • Create a test to examine each page while scrolling to make certain that the view coincides with the autofit-type. Presently, errors occur in page size when dealing with a long *.pdf file.

Just a note: (require 'cl) is out of date. Since emacs-24.3 it should be

(require ‘cl-lib) 

See http://www.emacswiki.org/emacs/CommonLispForEmacs

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top