;;; 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>
;; 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.
(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 (kbd "RET") 'image-next-line)
;; Zoom in/out.
(define-key map "+" 'doc-view-enlarge)
(define-key map "-" 'doc-view-shrink)
(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)
(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.
(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))
- (image-scroll-down nil)))))
+ (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
"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)
+ (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
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."
(defun doc-view-pdf->txt (pdf txt callback)
"Convert PDF to TXT asynchronously and call CALLBACK when finished."
+ (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-ps->pdf (ps pdf callback)
"Convert PS to PDF asynchronously and call CALLBACK when finished."
+ (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
;; resets during the redisplay).
(setq doc-view-pending-cache-flush t)
(let ((png-file (expand-file-name "page-%d.png"
+ (doc-view-current-cache-dir)))
+ (res-file (expand-file-name "resolution.el"
(doc-view-current-cache-dir))))
(make-directory (doc-view-current-cache-dir) t)
+ ;; Save the used resolution so that it can be restored when
+ ;; reading the cached files.
+ (let ((res doc-view-resolution))
+ (with-temp-buffer
+ (princ res (current-buffer))
+ ;; Don't use write-file, so as to avoid prompts for `require-newline',
+ ;; or for pre-existing buffers with the same name, ...
+ (write-region nil nil res-file nil 'silently)))
(case doc-view-doc-type
(dvi
;; DVI files have to be converted to PDF before Ghostscript can process
;;;; 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
`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)))
(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))
-
- (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 '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))
+ (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)