X-Git-Url: https://code.delx.au/gnu-emacs/blobdiff_plain/53d4c024e9407cfc884005f9c969f7488bc3ec99..2b34df4ebc935a834a77b930b35c4a42f7236440:/lisp/doc-view.el diff --git a/lisp/doc-view.el b/lisp/doc-view.el index 718040a8fa..3ea4fcecfd 100644 --- a/lisp/doc-view.el +++ b/lisp/doc-view.el @@ -1,6 +1,6 @@ ;;; 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 ;; Maintainer: Tassilo Horn @@ -8,10 +8,10 @@ ;; 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 @@ -19,15 +19,14 @@ ;; 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 . ;;; 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: @@ -63,7 +62,7 @@ ;; 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'). @@ -75,7 +74,7 @@ ;; `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. @@ -229,7 +228,7 @@ has finished." (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)) @@ -277,7 +276,7 @@ Can be `dvi', `pdf', or `ps'.") (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) @@ -290,13 +289,11 @@ Can be `dvi', `pdf', or `ps'.") (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 @@ -307,11 +304,6 @@ Can be `dvi', `pdf', or `ps'.") (define-key map (kbd "C-s") 'doc-view-search) (define-key map (kbd "") '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 @@ -355,7 +347,8 @@ Can be `dvi', `pdf', or `ps'.") (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) @@ -388,6 +381,7 @@ Can be `dvi', `pdf', or `ps'.") (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. @@ -427,20 +421,26 @@ Can be `dvi', `pdf', or `ps'.") (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 @@ -564,7 +564,10 @@ Should be invoked when the cached images aren't up-to-date." "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 @@ -637,6 +640,8 @@ Call CALLBACK with no arguments when done." 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." @@ -659,6 +664,8 @@ 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)) @@ -689,6 +696,8 @@ Start by converting PAGES, and then the rest." (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 @@ -716,8 +725,18 @@ Those files are saved in the directory given by the function ;; 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 @@ -737,6 +756,8 @@ Those files are saved in the directory given by the function ;;;; 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 @@ -842,16 +863,17 @@ have the page we want to view." (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 @@ -872,6 +894,8 @@ For now these keys are useful: `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))) @@ -1024,7 +1048,7 @@ If BACKWARD is non-nil, jump to the previous match." (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 @@ -1035,6 +1059,16 @@ 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 + (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 @@ -1044,11 +1078,14 @@ If BACKWARD is non-nil, jump to the previous match." "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) @@ -1074,93 +1111,105 @@ If BACKWARD is non-nil, jump to the previous match." ;;;###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-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 @@ -1189,29 +1238,29 @@ See the command `doc-view-mode' for more information on this 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)