;;; doc-view.el --- View PDF/PostScript/DVI files in Emacs
-;; Copyright (C) 2007, 2008 Free Software Foundation, Inc.
+;; Copyright (C) 2007, 2008, 2009 Free Software Foundation, Inc.
;;
;; Author: Tassilo Horn <tassilo@member.fsf.org>
;; Maintainer: Tassilo Horn <tassilo@member.fsf.org>
;; This file is part of GNU Emacs.
-;; GNU Emacs is free software; you can redistribute it and/or modify
+;; GNU Emacs is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
-;; the Free Software Foundation; either version 3, or (at your option)
-;; any later version.
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
;; GNU Emacs is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
-;; along with GNU Emacs; see the file COPYING. If not, write to the
-;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
-;; Boston, MA 02110-1301, USA.
+;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
;;; Requirements:
;; doc-view.el requires GNU Emacs 22.1 or newer. You also need Ghostscript,
-;; `dvipdfm' which comes with teTeX and `pdftotext', which comes with xpdf
-;; (http://www.foolabs.com/xpdf/) or poppler (http://poppler.freedesktop.org/).
+;; `dvipdf' (comes with Ghostscript) or `dvipdfm' (comes with teTeX or TeXLive)
+;; and `pdftotext', which comes with xpdf (http://www.foolabs.com/xpdf/) or
+;; poppler (http://poppler.freedesktop.org/).
;;; Commentary:
;; 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 invokation you
+;; `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').
;; `C-r' you can do the same, but backwards. To search for a new regexp give a
;; prefix arg to one of the search functions, e.g. by typing `C-u C-s'. The
;; searching works by using a plain text representation of the document. If
-;; that doesn't already exist the first invokation of `doc-view-search' (or
+;; that doesn't already exist the first invocation of `doc-view-search' (or
;; `doc-view-search-backward') starts the conversion. When that finishes and
;; you're still viewing the document (i.e. you didn't switch to another buffer)
;; you're queried for the regexp then.
;;; Code:
+(eval-when-compile (require 'cl))
(require 'dired)
(require 'image-mode)
(require 'jka-compr)
"Program to convert DVI files to PDF.
DVI file will be converted to PDF before the resulting PDF is
-converted to PNG."
+converted to PNG.
+
+If this and `doc-view-dvipdf-program' are set,
+`doc-view-dvipdf-program' will be preferred."
+ :type 'file
+ :group 'doc-view)
+
+(defcustom doc-view-dvipdf-program (executable-find "dvipdf")
+ "Program to convert DVI files to PDF.
+
+DVI file will be converted to PDF before the resulting PDF is
+converted to PNG.
+
+If this and `doc-view-dvipdfm-program' are set,
+`doc-view-dvipdf-program' will be preferred."
:type 'file
:group 'doc-view)
(let ((ol (image-mode-window-get 'overlay winprops)))
(if ol
(setq ol (copy-overlay ol))
- (assert (not (get-char-property (point-min) 'display (car winprops))))
+ (assert (not (get-char-property (point-min) 'display)))
(setq ol (make-overlay (point-min) (point-max) nil t))
(overlay-put ol 'doc-view t))
(overlay-put ol 'window (car winprops))
(defvar doc-view-current-files nil
"Only used internally.")
+(make-variable-buffer-local 'doc-view-current-files)
-(defvar doc-view-current-converter-process nil
+(defvar doc-view-current-converter-processes nil
"Only used internally.")
+(make-variable-buffer-local 'doc-view-current-converter-processes)
(defvar doc-view-current-timer nil
"Only used internally.")
+(make-variable-buffer-local 'doc-view-current-timer)
(defvar doc-view-current-cache-dir nil
"Only used internally.")
+(make-variable-buffer-local 'doc-view-current-cache-dir)
(defvar doc-view-current-search-matches nil
"Only used internally.")
+(make-variable-buffer-local 'doc-view-current-search-matches)
(defvar doc-view-pending-cache-flush nil
"Only used internally.")
(defvar doc-view-mode-map
(let ((map (make-sparse-keymap)))
- (suppress-keymap map)
+ (set-keymap-parent map image-mode-map)
;; Navigation in the document
(define-key map (kbd "n") 'doc-view-next-page)
(define-key map (kbd "p") 'doc-view-previous-page)
(define-key map (kbd "M-<") 'doc-view-first-page)
(define-key map (kbd "M->") 'doc-view-last-page)
(define-key map [remap goto-line] 'doc-view-goto-page)
- (define-key map [remap scroll-up] 'image-scroll-up)
- (define-key map [remap scroll-down] 'image-scroll-down)
+ (define-key map (kbd "RET") 'image-next-line)
;; Zoom in/out.
(define-key map "+" 'doc-view-enlarge)
(define-key map "-" 'doc-view-shrink)
- ;; Killing/burying the buffer (and the process)
- (define-key map (kbd "q") 'bury-buffer)
+ ;; 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 "C-s") 'doc-view-search)
(define-key map (kbd "<find>") 'doc-view-search)
(define-key map (kbd "C-r") 'doc-view-search-backward)
- ;; Scrolling
- (define-key map [remap forward-char] 'image-forward-hscroll)
- (define-key map [remap backward-char] 'image-backward-hscroll)
- (define-key map [remap next-line] 'image-next-line)
- (define-key map [remap previous-line] 'image-previous-line)
;; Show the tooltip
(define-key map (kbd "C-t") 'doc-view-show-tooltip)
;; Toggle between text and image display or editing
(defun doc-view-goto-page (page)
"View the page given by PAGE."
(interactive "nPage: ")
- (let ((len (length doc-view-current-files)))
+ (let ((len (length doc-view-current-files))
+ (hscroll (window-hscroll)))
(if (< page 1)
(setq page 1)
(when (and (> page len)
;; As long as the converter is running, we don't know
;; how many pages will be available.
- (null doc-view-current-converter-process))
+ (null doc-view-current-converter-processes))
(setq page len)))
(setf (doc-view-current-page) page
(doc-view-current-info)
(propertize
(format "Page %d of %d." page len) 'face 'bold)
;; Tell user if converting isn't finished yet
- (if doc-view-current-converter-process
+ (if doc-view-current-converter-processes
" (still converting...)\n"
"\n")
;; Display context infos if this page matches the last search
;; 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.
- (doc-view-insert-image (expand-file-name (format "page-%d.png" page)
- (doc-view-current-cache-dir))
- :pointer 'arrow)
+ (let ((file (expand-file-name (format "page-%d.png" 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
+ (lexical-let ((page page)
+ (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)
+ (with-selected-window win
+ (doc-view-goto-page page))))))))
(overlay-put (doc-view-current-overlay)
'help-echo (doc-view-current-info))))
(defun doc-view-scroll-up-or-next-page ()
"Scroll page up if possible, else goto next page."
(interactive)
- (when (= (window-vscroll) (image-scroll-up nil))
- (let ((cur-page (doc-view-current-page)))
+ (let ((hscroll (window-hscroll))
+ (cur-page (doc-view-current-page)))
+ (when (= (window-vscroll) (image-scroll-up nil))
(doc-view-next-page)
(when (/= cur-page (doc-view-current-page))
- (set-window-vscroll nil 0)))))
+ (image-bob)
+ (image-bol 1))
+ (set-window-hscroll (selected-window) hscroll))))
(defun doc-view-scroll-down-or-previous-page ()
"Scroll page down if possible, else goto previous page."
(interactive)
- (when (= (window-vscroll) (image-scroll-down nil))
- (let ((cur-page (doc-view-current-page)))
+ (let ((hscroll (window-hscroll))
+ (cur-page (doc-view-current-page)))
+ (when (= (window-vscroll) (image-scroll-down nil))
(doc-view-previous-page)
(when (/= cur-page (doc-view-current-page))
- (image-scroll-up nil)))))
+ (image-eob)
+ (image-bol 1))
+ (set-window-hscroll (selected-window) hscroll))))
;;;; Utility Functions
(defun doc-view-kill-proc ()
- "Kill the current converter process."
+ "Kill the current converter process(es)."
(interactive)
- (when doc-view-current-converter-process
+ (while doc-view-current-converter-processes
(ignore-errors ;; Maybe it's dead already?
- (kill-process doc-view-current-converter-process))
- (setq doc-view-current-converter-process nil))
+ (kill-process (pop doc-view-current-converter-processes))))
(when doc-view-current-timer
(cancel-timer doc-view-current-timer)
(setq doc-view-current-timer nil))
(cond
((eq type 'dvi)
(and (doc-view-mode-p 'pdf)
- doc-view-dvipdfm-program
- (executable-find doc-view-dvipdfm-program)))
+ (or (and doc-view-dvipdf-program
+ (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))
(and doc-view-ghostscript-program
"Generic sentinel for doc-view conversion processes."
(if (not (string-match "finished" event))
(message "DocView: process %s changed status to %s."
- (process-name proc) event)
- (with-current-buffer (process-get proc 'buffer)
- (setq doc-view-current-converter-process nil
- mode-line-process nil)
- (funcall (process-get proc 'callback)))))
+ (process-name proc)
+ (if (string-match "\\(.+\\)\n?\\'" event)
+ (match-string 1 event)
+ event))
+ (when (buffer-live-p (process-get proc 'buffer))
+ (with-current-buffer (process-get proc 'buffer)
+ (setq doc-view-current-converter-processes
+ (delq proc doc-view-current-converter-processes))
+ (setq mode-line-process
+ (if doc-view-current-converter-processes
+ (format ":%s" (car doc-view-current-converter-processes))))
+ (funcall (process-get proc 'callback))))))
+
+(defun doc-view-start-process (name program args callback)
+ ;; Make sure the process is started in an existing directory, (rather than
+ ;; some file-name-handler-managed dir, for example).
+ (let* ((default-directory (if (file-readable-p default-directory)
+ default-directory
+ (expand-file-name "~/")))
+ (proc (apply 'start-process name doc-view-conversion-buffer
+ program args)))
+ (push proc doc-view-current-converter-processes)
+ (setq mode-line-process (list (format ":%s" proc)))
+ (set-process-sentinel proc 'doc-view-sentinel)
+ (process-put proc 'buffer (current-buffer))
+ (process-put proc 'callback callback)))
(defun doc-view-dvi->pdf (dvi pdf callback)
"Convert DVI to PDF asynchronously and call CALLBACK when finished."
- (setq doc-view-current-converter-process
- (start-process "dvi->pdf" doc-view-conversion-buffer
- doc-view-dvipdfm-program
- "-o" pdf dvi)
- mode-line-process (list (format ":%s" doc-view-current-converter-process)))
- (set-process-sentinel doc-view-current-converter-process 'doc-view-sentinel)
- (process-put doc-view-current-converter-process 'buffer (current-buffer))
- (process-put doc-view-current-converter-process 'callback callback))
-
-(defun doc-view-pdf/ps->png-sentinel (proc event)
- "If PDF/PS->PNG conversion was successful, update the display."
- (if (not (string-match "finished" event))
- (message "DocView: converter process changed status to %s." event)
- ;; FIXME: kill the process if we kill the buffer?
- (when (buffer-live-p (process-get proc 'buffer))
- (with-current-buffer (process-get proc 'buffer)
- (setq doc-view-current-converter-process nil
- mode-line-process nil)
- (when doc-view-current-timer
- (cancel-timer doc-view-current-timer)
- (setq doc-view-current-timer nil))
- ;; Yippie, finished. Update the display!
- (doc-view-display (current-buffer) 'force)))))
+ ;; Prefer dvipdf over dvipdfm, because the latter has problems if the DVI
+ ;; references and includes other PS files.
+ (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)
+ (doc-view-start-process "dvi->pdf" doc-view-dvipdfm-program
+ (list "-o" pdf dvi)
+ callback)))
+
(defun doc-view-pdf/ps->png (pdf-ps png)
"Convert PDF-PS to PNG asynchronously."
- (setq doc-view-current-converter-process
- ;; Make sure the process is started in an existing directory,
- ;; (rather than some file-name-handler-managed dir, for example).
- (let ((default-directory (file-name-directory pdf-ps)))
- (apply 'start-process
- (append (list "pdf/ps->png" doc-view-conversion-buffer
- doc-view-ghostscript-program)
- doc-view-ghostscript-options
- (list (format "-r%d" (round doc-view-resolution)))
- (list (concat "-sOutputFile=" png))
- (list pdf-ps))))
- mode-line-process (list (format ":%s" doc-view-current-converter-process)))
- (process-put doc-view-current-converter-process
- 'buffer (current-buffer))
- (set-process-sentinel doc-view-current-converter-process
- 'doc-view-pdf/ps->png-sentinel)
+ (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))
+ (lambda ()
+ (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
- (run-at-time "1 secs" doc-view-conversion-refresh-interval
- 'doc-view-display
- (current-buffer)))))
+ (run-at-time "1 secs" doc-view-conversion-refresh-interval
+ '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.
+Start by converting PAGES, and then the rest."
+ (if (null pages)
+ (doc-view-pdf/ps->png pdf png)
+ ;; We could render several `pages' with a single process if they're
+ ;; (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)))
+ (doc-view-pdf->png-1
+ pdf (format png (car pages)) (car pages)
+ (lambda ()
+ (if rest
+ (doc-view-pdf->png pdf png rest)
+ ;; Yippie, the important pages are done, update the display.
+ (clear-image-cache)
+ ;; Convert the rest of the pages.
+ (doc-view-pdf/ps->png pdf png)))))))
(defun doc-view-pdf->txt (pdf txt callback)
"Convert PDF to TXT asynchronously and call CALLBACK when finished."
- (setq doc-view-current-converter-process
- (start-process "pdf->txt" doc-view-conversion-buffer
- doc-view-pdftotext-program "-raw"
- pdf txt)
- mode-line-process (list (format ":%s" doc-view-current-converter-process)))
- (set-process-sentinel doc-view-current-converter-process
- 'doc-view-sentinel)
- (process-put doc-view-current-converter-process 'buffer (current-buffer))
- (process-put doc-view-current-converter-process 'callback callback))
+ (or 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-doc->txt (txt callback)
"Convert the current document to text and call CALLBACK when done."
- (unless (file-exists-p (doc-view-current-cache-dir))
- (make-directory (doc-view-current-cache-dir)))
+ (make-directory (doc-view-current-cache-dir) t)
(case doc-view-doc-type
(pdf
;; Doc is a PDF, so convert it to TXT
(defun doc-view-ps->pdf (ps pdf callback)
"Convert PS to PDF asynchronously and call CALLBACK when finished."
- (setq doc-view-current-converter-process
- (start-process "ps->pdf" doc-view-conversion-buffer
- doc-view-ps2pdf-program
- ;; Avoid security problems when rendering files from
- ;; untrusted sources.
- "-dSAFER"
- ;; in-file and out-file
- ps pdf)
- mode-line-process (list (format ":%s" doc-view-current-converter-process)))
- (set-process-sentinel doc-view-current-converter-process 'doc-view-sentinel)
- (process-put doc-view-current-converter-process 'buffer (current-buffer))
- (process-put doc-view-current-converter-process 'callback callback))
+ (or 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
+ ;; Avoid security problems when rendering files from
+ ;; untrusted sources.
+ "-dSAFER"
+ ;; in-file and out-file
+ ps pdf)
+ callback))
+
+(defun doc-view-active-pages ()
+ (let ((pages ()))
+ (dolist (win (get-buffer-window-list (current-buffer) nil 'visible))
+ (let ((page (image-mode-window-get 'page win)))
+ (unless (memq page pages) (push page pages))))
+ pages))
(defun doc-view-convert-current-doc ()
"Convert `doc-view-buffer-file-name' to a set of png files, one file per page.
;; 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))))
- (unless (file-exists-p (doc-view-current-cache-dir))
- (make-directory (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
(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)))
(t
;; Convert to PNG images.
(doc-view-pdf/ps->png doc-view-buffer-file-name png-file)))))
;;;; Slicing
+(declare-function image-size "image.c" (spec &optional pixels frame))
+
(defun doc-view-set-slice (x y width height)
"Set the slice of the images that should be displayed.
You can use this function to tell doc-view not to display the
(list (cons 'slice slice) image)
image))
;; We're trying to display a page that doesn't exist.
- (doc-view-current-converter-process
+ (doc-view-current-converter-processes
;; Maybe the page doesn't exist *yet*.
"Cannot display this page (yet)!")
(t
(sort (directory-files (doc-view-current-cache-dir) t
"page-[0-9]+\\.png" t)
'doc-view-sort))
- (dolist (win (get-buffer-window-list buffer nil 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))))))))
+ (dolist (win (or (get-buffer-window-list buffer nil t)
+ (list (selected-window))))
+ (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))))))))
(defun doc-view-buffer-message ()
;; Only show this message initially, not when refreshing the buffer (in which
`k' : Kill the conversion process and this buffer.
`K' : Kill the conversion process.\n"))))
+(declare-function tooltip-show "tooltip" (text &optional use-echo-area))
+
(defun doc-view-show-tooltip ()
(interactive)
(tooltip-show (doc-view-current-info)))
(defun doc-view-open-text ()
"Open a buffer with the current doc's contents as text."
(interactive)
- (if doc-view-current-converter-process
+ (if doc-view-current-converter-processes
(message "DocView: please wait till conversion finished.")
(let ((txt (expand-file-name "doc.txt" (doc-view-current-cache-dir))))
(if (file-readable-p txt)
(doc-view-search-no-of-matches
doc-view-current-search-matches)))
;; We must convert to TXT first!
- (if doc-view-current-converter-process
+ (if doc-view-current-converter-processes
(message "DocView: please wait till conversion finished.")
(doc-view-doc->txt txt (lambda () (doc-view-search nil))))))))
(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))
- (> 0 (length (directory-files (doc-view-current-cache-dir) nil "\\.png$")))))
+ (> (length (directory-files (doc-view-current-cache-dir) nil "\\.png$")) 0)))
(defun doc-view-initiate-display ()
;; Switch to image display if possible
- (if (doc-view-mode-p (intern (file-name-extension doc-view-buffer-file-name)))
+ (if (doc-view-mode-p doc-view-doc-type)
(progn
(doc-view-buffer-message)
(setf (doc-view-current-page) (or (doc-view-current-page) 1))
(if (doc-view-already-converted-p)
(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)
+ (set (make-local-variable 'doc-view-resolution) res)))
(doc-view-display (current-buffer) 'force))
(doc-view-convert-current-doc))
(message
"editing or viewing the document."))))
(message
"%s"
- (substitute-command-keys
- (concat "No image (png) support available or some conversion utility for "
- (file-name-extension doc-view-buffer-file-name)" files is missing. "
- "Type \\[doc-view-toggle-display] to switch to an editing mode or "
- "\\[doc-view-open-text] to open a buffer showing the doc as text.")))))
+ (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))))
(defvar bookmark-make-record-function)
;;;###autoload
(defun doc-view-mode ()
"Major mode in DocView buffers.
+
+DocView Mode is an Emacs document viewer. It displays PDF, PS
+and DVI files (as PNG images) in Emacs buffers.
+
You can use \\<doc-view-mode-map>\\[doc-view-toggle-display] to
toggle between displaying the document or editing it as text.
\\{doc-view-mode-map}"
(interactive)
- (let* ((prev-major-mode (if (eq major-mode 'doc-view-mode)
- doc-view-previous-major-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")))))
-
- (doc-view-make-safe-dir doc-view-cache-directory)
- ;; Handle compressed files, remote files, files inside archives
- (set (make-local-variable 'doc-view-buffer-file-name)
- (cond
- (jka-compr-really-do-compress
- (expand-file-name
- (file-name-nondirectory
- (file-name-sans-extension buffer-file-name))
- doc-view-cache-directory))
- ;; Is the file readable by local processes?
- ;; We used to use `file-remote-p' but it's unclear what it's
- ;; supposed to return nil for things like local files accessed via
- ;; `su' or via file://...
- ((let ((file-name-handler-alist nil))
- (not (file-readable-p buffer-file-name)))
- (expand-file-name
- (file-name-nondirectory buffer-file-name)
- doc-view-cache-directory))
- (t buffer-file-name)))
- (when (not (string= doc-view-buffer-file-name buffer-file-name))
- (write-region nil nil doc-view-buffer-file-name))
-
- (make-local-variable 'doc-view-current-files)
- (make-local-variable 'doc-view-current-converter-process)
- (make-local-variable 'doc-view-current-timer)
- (make-local-variable 'doc-view-current-cache-dir)
- (make-local-variable 'doc-view-current-search-matches)
- (add-hook 'change-major-mode-hook
- (lambda () (remove-overlays (point-min) (point-max) 'doc-view t))
- nil t)
- (add-hook 'clone-indirect-buffer-hook 'doc-view-clone-buffer-hook nil t)
-
- (remove-overlays (point-min) (point-max) 'doc-view t) ;Just in case.
- ;; Keep track of display info ([vh]scroll, page number, overlay, ...)
- ;; for each window in which this document is shown.
- (add-hook 'image-mode-new-window-functions
- '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 (length doc-view-current-files)))))
- ;; Don't scroll unless the user specifically asked for it.
- (set (make-local-variable 'auto-hscroll-mode) nil)
- (set (make-local-variable 'cursor-type) nil)
- (use-local-map doc-view-mode-map)
- (set (make-local-variable 'after-revert-hook) 'doc-view-reconvert-doc)
- (set (make-local-variable 'bookmark-make-record-function)
- 'doc-view-bookmark-make-record)
- (setq mode-name "DocView"
- buffer-read-only t
- major-mode 'doc-view-mode)
- (doc-view-initiate-display)
- (run-mode-hooks 'doc-view-mode-hook))
+ (if (= (point-min) (point-max))
+ ;; 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))
+
+ (let* ((prev-major-mode (if (eq major-mode 'doc-view-mode)
+ doc-view-previous-major-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")))))
+
+ (doc-view-make-safe-dir doc-view-cache-directory)
+ ;; Handle compressed files, remote files, files inside archives
+ (set (make-local-variable 'doc-view-buffer-file-name)
+ (cond
+ (jka-compr-really-do-compress
+ (expand-file-name
+ (file-name-nondirectory
+ (file-name-sans-extension buffer-file-name))
+ doc-view-cache-directory))
+ ;; Is the file readable by local processes?
+ ;; We used to use `file-remote-p' but it's unclear what it's
+ ;; supposed to return nil for things like local files accessed via
+ ;; `su' or via file://...
+ ((let ((file-name-handler-alist nil))
+ (not (file-readable-p buffer-file-name)))
+ (expand-file-name
+ (file-name-nondirectory buffer-file-name)
+ doc-view-cache-directory))
+ (t buffer-file-name)))
+ (when (not (string= doc-view-buffer-file-name buffer-file-name))
+ (write-region nil nil doc-view-buffer-file-name))
+
+ (add-hook 'change-major-mode-hook
+ (lambda ()
+ (doc-view-kill-proc)
+ (remove-overlays (point-min) (point-max) 'doc-view t))
+ nil t)
+ (add-hook 'clone-indirect-buffer-hook 'doc-view-clone-buffer-hook nil t)
+ (add-hook 'kill-buffer-hook 'doc-view-kill-proc nil t)
+
+ (remove-overlays (point-min) (point-max) 'doc-view t) ;Just in case.
+ ;; Keep track of display info ([vh]scroll, page number, overlay,
+ ;; ...) for each window in which this document is shown.
+ (add-hook 'image-mode-new-window-functions
+ '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 (length doc-view-current-files)))))
+ ;; Don't scroll unless the user specifically asked for it.
+ (set (make-local-variable 'auto-hscroll-mode) nil)
+ (set (make-local-variable 'cursor-type) nil)
+ (use-local-map doc-view-mode-map)
+ (set (make-local-variable 'after-revert-hook) 'doc-view-reconvert-doc)
+ (set (make-local-variable 'bookmark-make-record-function)
+ 'doc-view-bookmark-make-record)
+ (setq mode-name "DocView"
+ buffer-read-only t
+ major-mode 'doc-view-mode)
+ (doc-view-initiate-display)
+ (run-mode-hooks 'doc-view-mode-hook)))
;;;###autoload
(define-minor-mode doc-view-minor-mode
;;;; Bookmark integration
-(declare-function bookmark-buffer-file-name "bookmark" ())
+(declare-function bookmark-make-record-default "bookmark"
+ (&optional point-only))
(declare-function bookmark-prop-get "bookmark" (bookmark prop))
+(declare-function bookmark-default-handler "bookmark" (bmk))
(defun doc-view-bookmark-make-record ()
- `((filename . ,(bookmark-buffer-file-name))
- (page . ,(doc-view-current-page))
- (handler . doc-view-bookmark-jump)))
+ (nconc (bookmark-make-record-default)
+ `((page . ,(doc-view-current-page))
+ (handler . doc-view-bookmark-jump))))
;;;###autoload
(defun doc-view-bookmark-jump (bmk)
;; This implements the `handler' function interface for record type
;; returned by `doc-view-bookmark-make-record', which see.
- (let ((filename (bookmark-prop-get bmk 'filename))
- (page (bookmark-prop-get bmk 'page)))
- (with-current-buffer (find-file-noselect filename)
+ (prog1 (bookmark-default-handler bmk)
+ (let ((page (bookmark-prop-get bmk 'page)))
(when (not (eq major-mode 'doc-view-mode))
- (doc-view-toggle-display))
+ (doc-view-toggle-display))
(with-selected-window
(or (get-buffer-window (current-buffer) 0)
(selected-window))
- (doc-view-goto-page page))
- `((buffer ,(current-buffer)) (position ,1)))))
+ (doc-view-goto-page page)))))
(provide 'doc-view)