Mercurial > emacs
changeset 84422:67c730c1c685
Initial revision
author | Thien-Thi Nguyen <ttn@gnuvola.org> |
---|---|
date | Sun, 09 Sep 2007 22:28:18 +0000 |
parents | 1b8ef9d5bfaa |
children | a315dc8105bc |
files | lisp/doc-view.el |
diffstat | 1 files changed, 762 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/doc-view.el Sun Sep 09 22:28:18 2007 +0000 @@ -0,0 +1,762 @@ +;;; doc-view.el --- View PDF/PostScript/DVI files in Emacs + +;; Copyright (C) 2007 Free Software Foundation, Inc. +;; +;; Author: Tassilo Horn <tassilo@member.fsf.org> +;; Maintainer: Tassilo Horn <tassilo@member.fsf.org> +;; Keywords: files, pdf, ps, dvi +;; Version: <2007-09-07 Fri 15:28> + +;; This file is part of GNU Emacs. + +;; 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. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; 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. + +;;; 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 poppler +;; (http://poppler.freedesktop.org/). + +;;; Commentary: + +;; DocView is a document viewer for Emacs. It converts PDF, PS and DVI files +;; to a set of PNG files, one PNG for each page, and displays the PNG images +;; inside an Emacs buffer. This buffer uses `doc-view-mode' which provides +;; convenient key bindings for browsing the document. +;; +;; To use it simply do +;; +;; M-x doc-view RET +;; +;; and you'll be queried for a document to open. +;; +;; Since conversion may take some time all the PNG images are cached in a +;; subdirectory of `doc-view-cache-directory' and reused when you want to view +;; that file again. This reusing can be omitted if you provide a prefx +;; argument to `doc-view'. To delete all cached files use +;; `doc-view-clear-cache'. To open the cache with dired, so that you can tidy +;; it out use `doc-view-dired-cache'. +;; +;; When conversion in underway the first page will be displayed as soon as it +;; is available and the available pages are refreshed every +;; `doc-view-conversion-refresh-interval' seconds. If that variable is nil the +;; 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 invokation 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'). +;; +;; Dired users should have a look at `doc-view-dired'. +;; +;; 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 +;; matching pages and messages how many match-pages were found. After that you +;; can jump to the next page containing a match with +;; `doc-view-search-next-match' (bound to `C-S-n') or to the previous matching +;; page with `doc-view-search-previous-match' (bound to `C-S-p'). This works +;; by searching a plain text representation of the document. If that doesn't +;; already exist the first invokation of `doc-view-search' 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. + +;;; Configuration: + +;; Basically doc-view should be quite usable with its standard settings, so +;; putting +;; +;; (require 'doc-view) +;; +;; into your `user-init-file' should be enough. If the images are too small or +;; too big you should set the "-rXXX" option in `doc-view-ghostscript-options' +;; to another value. (The bigger your screen, the higher the value.) +;; +;; This and all other options can be set with the customization interface. +;; Simply do +;; +;; M-x customize-group RET doc-view RET +;; +;; and modify them to your needs. + +;;; Code: + +(require 'dired) +(eval-when-compile (require 'cl)) + +;;;; Customization Options + +(defgroup doc-view nil + "In-buffer viewer for PDF, PostScript and DVI files." + :link '(function-link doc-view) + :version "22.2" + :group 'applications + :group 'multimedia + :prefix "doc-view-") + +(defcustom doc-view-ghostscript-program "gs" + "Program to convert PS and PDF files to PNG." + :type '(file) + :group 'doc-view) + +(defcustom doc-view-ghostscript-options + '("-dNOPAUSE" "-sDEVICE=png16m" "-dTextAlphaBits=4" + "-dBATCH" "-dGraphicsAlphaBits=4" "-dQUIET" + "-r100") + "A list of options to give to ghostview." + :type '(sexp) + :group 'doc-view) + +(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 +converted to PNG." + :type '(file) + :group 'doc-view) + +(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 "pdftotext" + "Program to convert PDF files to plain text. + +Needed for searching." + :type '(file) + :group 'doc-view) + +(defcustom doc-view-cache-directory (concat temporary-file-directory + "doc-view") + "The base directory, where the PNG images will be saved." + :type '(directory) + :group 'doc-view) + +(defcustom doc-view-conversion-buffer "*doc-view conversion output*" + "The buffer where messages from the converter programs go to." + :type '(string) + :group 'doc-view) + +(defcustom doc-view-conversion-refresh-interval 3 + "Every how much seconds the DocView buffer gets refreshed while conversion. +After such an refresh newly converted pages will be available for +viewing. If set to nil there won't be any refreshes and the +pages won't be displayed before conversion of the whole document +has finished." + :type '(string) + :group 'doc-view) + +;;;; Internal Variables + +(defvar doc-view-current-files nil + "Only used internally.") + +(defvar doc-view-current-page nil + "Only used internally.") + +(defvar doc-view-current-doc nil + "Only used internally.") + +(defvar doc-view-current-converter-process nil + "Only used internally.") + +(defvar doc-view-current-timer nil + "Only used internally.") + +(defvar doc-view-current-slice nil + "Only used internally.") + +(defvar doc-view-current-cache-dir nil + "Only used internally.") + +(defvar doc-view-current-search-matches nil + "Only used internally.") + +(defvar doc-view-current-image nil + "Only used internally.") + +(defvar doc-view-current-info nil + "Only used internally.") + +;;;; DocView Keymap + +(defvar doc-view-mode-map + (let ((map (make-sparse-keymap))) + ;; 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 "<next>") 'doc-view-next-page) + (define-key map (kbd "<prior>") 'doc-view-previous-page) + (define-key map (kbd "SPC") 'doc-view-scroll-up-or-next-page) + (define-key map (kbd "DEL") 'doc-view-scroll-down-or-previous-page) + (define-key map (kbd "M-<") 'doc-view-first-page) + (define-key map (kbd "M->") 'doc-view-last-page) + (define-key map (kbd "g") 'doc-view-goto-page) + ;; Killing/burying the buffer (and the process) + (define-key map (kbd "q") 'bury-buffer) + (define-key map (kbd "k") 'doc-view-kill-proc-and-buffer) + (define-key map (kbd "C-x k") 'doc-view-kill-proc-and-buffer) + ;; 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 r") 'doc-view-reset-slice) + ;; Searching + (define-key map (kbd "C-s") 'doc-view-search) + (define-key map (kbd "<find>") 'doc-view-search) + (define-key map (kbd "C-S-n") 'doc-view-search-next-match) + (define-key map (kbd "C-S-p") 'doc-view-search-previous-match) + ;; Scrolling + (define-key map (kbd "C-v") 'scroll-up) + (define-key map (kbd "<mouse-4>") 'mwheel-scroll) + (define-key map (kbd "<mouse-5>") 'mwheel-scroll) + (define-key map (kbd "M-v") 'scroll-down) + ;; Show the tooltip + (define-key map (kbd "C-t") 'doc-view-show-tooltip) + (suppress-keymap map) + map) + "Keymap used by `doc-view-mode'.") + +;;;; Navigation Commands + +(defun doc-view-goto-page (page) + "View the page given by PAGE." + (interactive "nPage: ") + (let ((len (length doc-view-current-files))) + (if (< page 1) + (setq page 1) + (when (> page len) + (setq page len))) + (setq doc-view-current-page page + doc-view-current-info + (concat + (propertize + (format "Page %d of %d." + doc-view-current-page + len) 'face 'bold) + ;; Tell user if converting isn't finished yet + (if doc-view-current-converter-process + " (still converting...)\n" + "\n") + ;; Display context infos if this page matches the last search + (when (and doc-view-current-search-matches + (assq doc-view-current-page + doc-view-current-search-matches)) + (concat (propertize "Search matches:\n" 'face 'bold) + (let ((contexts "")) + (dolist (m (cdr (assq doc-view-current-page + doc-view-current-search-matches))) + (setq contexts (concat contexts " - \"" m "\"\n"))) + contexts))))) + ;; Update the buffer + (setq inhibit-read-only t) + (erase-buffer) + (let ((beg (point))) + (doc-view-insert-image (nth (1- page) doc-view-current-files) + :pointer 'arrow) + (put-text-property beg (point) 'help-echo doc-view-current-info)) + (insert "\n" doc-view-current-info) + (goto-char (point-min)) + (forward-char) + (setq inhibit-read-only nil))) + +(defun doc-view-next-page (&optional arg) + "Browse ARG pages forward." + (interactive "p") + (doc-view-goto-page (+ doc-view-current-page (or arg 1)))) + +(defun doc-view-previous-page (&optional arg) + "Browse ARG pages backward." + (interactive "p") + (doc-view-goto-page (- doc-view-current-page (or arg 1)))) + +(defun doc-view-first-page () + "View the first page." + (interactive) + (doc-view-goto-page 1)) + +(defun doc-view-last-page () + "View the last page." + (interactive) + (doc-view-goto-page (length doc-view-current-files))) + +(defun doc-view-scroll-up-or-next-page () + "Scroll page up if possible, else goto next page." + (interactive) + (condition-case nil + (scroll-up) + (error (doc-view-next-page)))) + +(defun doc-view-scroll-down-or-previous-page () + "Scroll page down if possible, else goto previous page." + (interactive) + (condition-case nil + (scroll-down) + (error (doc-view-previous-page) + (goto-char (point-max))))) + +(defun doc-view-kill-proc-and-buffer () + "Kill the current converter process and buffer." + (interactive) + (when (eq major-mode 'doc-view-mode) + (when doc-view-current-converter-process + (kill-process doc-view-current-converter-process)) + (when doc-view-current-timer + (cancel-timer doc-view-current-timer) + (setq doc-view-current-timer nil)) + (kill-buffer (current-buffer)))) + +;;;; Conversion Functions + +(defun doc-view-file-name-to-directory-name (file) + "Return the directory where the png files of FILE should be saved. + +It'a a subdirectory of `doc-view-cache-directory'." + (if doc-view-current-cache-dir + doc-view-current-cache-dir + (file-name-as-directory + (concat (file-name-as-directory doc-view-cache-directory) + (with-temp-buffer + (insert-file-contents-literally file) + (md5 (current-buffer))))))) + +(defun doc-view-dvi->pdf-sentinel (proc event) + "If DVI->PDF conversion was successful, convert the PDF to PNG +now." + (if (not (string-match "finished" event)) + (message "DocView: dvi->pdf process changed status to %s." event) + (set-buffer (process-get proc 'buffer)) + (setq doc-view-current-converter-process nil) + (message "DocView: finished conversion from DVI to PDF!") + ;; Now go on converting this PDF to a set of PNG files. + (let* ((pdf (process-get proc 'pdf-file)) + (png (concat (doc-view-file-name-to-directory-name + doc-view-current-doc) + "page-%d.png"))) + (doc-view-pdf/ps->png pdf png)))) + +(defun doc-view-dvi->pdf (dvi pdf) + "Convert DVI to PDF asynchrounously." + (message "DocView: converting DVI to PDF now!") + (setq doc-view-current-converter-process + (start-process "doc-view-dvi->pdf" doc-view-conversion-buffer + doc-view-dvipdfm-program + "-o" pdf dvi)) + (set-process-sentinel doc-view-current-converter-process + 'doc-view-dvi->pdf-sentinel) + (process-put doc-view-current-converter-process 'buffer (current-buffer)) + (process-put doc-view-current-converter-process 'pdf-file pdf)) + +(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) + (set-buffer (process-get proc 'buffer)) + (setq doc-view-current-converter-process nil) + (when doc-view-current-timer + (cancel-timer doc-view-current-timer) + (setq doc-view-current-timer nil)) + (message "DocView: finished conversion from PDF/PS to PNG!") + ;; Yippie, finished. Update the display! + (doc-view-display doc-view-current-doc))) + +(defun doc-view-pdf/ps->png (pdf-ps png) + "Convert PDF-PS to PNG asynchrounously." + (message "DocView: converting PDF or PS to PNG now!") + (setq doc-view-current-converter-process + (apply 'start-process + (append (list "doc-view-pdf/ps->png" doc-view-conversion-buffer + doc-view-ghostscript-program) + doc-view-ghostscript-options + (list (concat "-sOutputFile=" png)) + (list pdf-ps)))) + (process-put doc-view-current-converter-process + 'buffer (current-buffer)) + (set-process-sentinel doc-view-current-converter-process + 'doc-view-pdf/ps->png-sentinel) + (when doc-view-conversion-refresh-interval + (setq doc-view-current-timer + (run-at-time "1 secs" doc-view-conversion-refresh-interval + 'doc-view-display + doc-view-current-doc)))) + +(defun doc-view-pdf->txt-sentinel (proc event) + (if (not (string-match "finished" event)) + (message "DocView: converter process changed status to %s." event) + (let ((current-buffer (current-buffer)) + (proc-buffer (process-get proc 'buffer))) + (set-buffer proc-buffer) + (setq doc-view-current-converter-process nil) + (message "DocView: finished conversion from PDF to TXT!") + ;; If the user looks at the DocView buffer where the conversion was + ;; performed, search anew. This time it will be queried for a regexp. + (when (eq current-buffer proc-buffer) + (doc-view-search))))) + +(defun doc-view-pdf->txt (pdf txt) + "Convert PDF to TXT asynchrounously." + (message "DocView: converting PDF to TXT now!") + (setq doc-view-current-converter-process + (start-process "doc-view-pdf->txt" doc-view-conversion-buffer + doc-view-pdftotext-program "-raw" + pdf txt)) + (set-process-sentinel doc-view-current-converter-process + 'doc-view-pdf->txt-sentinel) + (process-put doc-view-current-converter-process 'buffer (current-buffer))) + +(defun doc-view-ps->pdf-sentinel (proc event) + (if (not (string-match "finished" event)) + (message "DocView: converter process changed status to %s." event) + (set-buffer (process-get proc 'buffer)) + (setq doc-view-current-converter-process nil) + (message "DocView: finished conversion from PS to PDF!") + ;; Now we can transform to plain text. + (doc-view-pdf->txt (process-get proc 'pdf-file) + (concat (doc-view-file-name-to-directory-name + doc-view-current-doc) + "doc.txt")))) + +(defun doc-view-ps->pdf (ps pdf) + "Convert PS to PDF asynchronously." + (message "DocView: converting PS to PDF now!") + (setq doc-view-current-converter-process + (start-process "doc-view-ps->pdf" doc-view-conversion-buffer + doc-view-ps2pdf-program + ps pdf)) + (set-process-sentinel doc-view-current-converter-process + 'doc-view-ps->pdf-sentinel) + (process-put doc-view-current-converter-process 'buffer (current-buffer)) + (process-put doc-view-current-converter-process 'pdf-file pdf)) + +(defun doc-view-convert-doc (doc) + "Convert DOC to a set of png files, one file per page. + +Those files are saved in the directory given by +`doc-view-file-name-to-directory-name'." + (clear-image-cache) + (let* ((dir (doc-view-file-name-to-directory-name doc)) + (png-file (concat (file-name-as-directory dir) "page-%d.png"))) + (when (file-exists-p dir) + (dired-delete-file dir 'always)) + (make-directory dir t) + (if (not (string= (file-name-extension doc) "dvi")) + ;; Convert to PNG images. + (doc-view-pdf/ps->png doc png-file) + ;; DVI files have to be converted to PDF before GhostScript can process + ;; it. + (doc-view-dvi->pdf doc + (concat (file-name-as-directory dir) + "doc.pdf"))))) + +;;;; DocView Mode + +(define-derived-mode doc-view-mode nil "DocView" + "Major mode in DocView buffers. + +\\{doc-view-mode-map}" + :group 'doc-view + (setq buffer-read-only t) + (make-local-variable 'doc-view-current-files) + (make-local-variable 'doc-view-current-doc) + (make-local-variable 'doc-view-current-image) + (make-local-variable 'doc-view-current-page) + (make-local-variable 'doc-view-current-converter-process) + (make-local-variable 'doc-view-current-timer) + (make-local-variable 'doc-view-current-slice) + (make-local-variable 'doc-view-current-cache-dir) + (make-local-variable 'doc-view-current-info) + (make-local-variable 'doc-view-current-search-matches)) + +;;;; Slicing + +(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 +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'." + (interactive + (let* ((size (image-size doc-view-current-image t)) + (a (read-number (format "Top-left X (0..%d): " (car size)))) + (b (read-number (format "Top-left Y (0..%d): " (cdr size)))) + (c (read-number (format "Width (0..%d): " (- (car size) a)))) + (d (read-number (format "Height (0..%d): " (- (cdr size) b))))) + (list a b c d))) + (setq doc-view-current-slice (list x y width height)) + ;; Redisplay + (doc-view-goto-page doc-view-current-page)) + +(defun doc-view-set-slice-using-mouse () + "Set the slice of the images that should be displayed. +You set the slice by pressing mouse-1 at its top-left corner and +dragging it to its bottom-right corner. See also +`doc-view-set-slice' and `doc-view-reset-slice'." + (interactive) + (let (x y w h done) + (while (not done) + (let ((e (read-event + (concat "Press mouse-1 at the top-left corner and " + "drag it to the bottom-right corner!")))) + (when (eq (car e) 'drag-mouse-1) + (setq x (car (posn-object-x-y (event-start e)))) + (setq y (cdr (posn-object-x-y (event-start e)))) + (setq w (- (car (posn-object-x-y (event-end e))) x)) + (setq h (- (cdr (posn-object-x-y (event-end e))) y)) + (setq done t)))) + (doc-view-set-slice x y w h))) + +(defun doc-view-reset-slice () + "Resets the current slice. +After calling this function the whole pages will be visible +again." + (interactive) + (setq doc-view-current-slice nil) + ;; Redisplay + (doc-view-goto-page doc-view-current-page)) + +;;;; Display + +(defun doc-view-insert-image (file &rest args) + "Insert the given png FILE. +ARGs is a list of image descriptors." + (let ((image (apply 'create-image file 'png nil args))) + (setq doc-view-current-image image) + (insert-image image (concat "[" file "]") nil doc-view-current-slice))) + +(defun doc-view-sort (a b) + "Return non-nil if A should be sorted before B. +Predicate for sorting `doc-view-current-files'." + (if (< (length a) (length b)) + t + (if (> (length a) (length b)) + nil + (string< a b)))) + +(defun doc-view-display (doc) + "Start viewing the document DOC." + (let ((dir (doc-view-file-name-to-directory-name doc))) + (set-buffer (format "*DocView: %s*" doc)) + (setq doc-view-current-files + (sort (directory-files dir t "page-[0-9]+\\.png" t) + 'doc-view-sort)) + (when (> (length doc-view-current-files) 0) + (doc-view-goto-page doc-view-current-page)))) + +(defun doc-view-buffer-message () + (setq inhibit-read-only t) + (erase-buffer) + (insert (propertize "Welcome to DocView!" 'face 'bold) + "\n" + " +If you see this buffer it means that the document you want to +view gets converted to PNG now and the conversion of the first +page hasn't finished yet or +`doc-view-conversion-refresh-interval' is set to nil. + +For now these keys are useful: + + `q' : Bury this buffer. Conversion will go on in background. + `k' : Kill the conversion process and this buffer.\n") + (setq inhibit-read-only nil)) + +(defun doc-view-show-tooltip () + (interactive) + (tooltip-show doc-view-current-info)) + +;;;; Searching + +(defun doc-view-search-internal (regexp file) + "Return a list of FILE's pages that contain text matching REGEXP. +The value is an alist of the form + + (PAGE CONTEXTS) + +where PAGE is the pagenumber and CONTEXTS are the lines +containing the match." + (with-temp-buffer + (insert-file-contents file) + (let ((page 1) + (lastpage 1) + matches) + (while (re-search-forward (concat "\\(?:\\([]\\)\\|\\(" + regexp "\\)\\)") nil t) + (when (match-string 1) (incf page)) + (when (match-string 2) + (if (/= page lastpage) + (setq matches (push (cons page + (list (buffer-substring + (line-beginning-position) + (line-end-position)))) + matches)) + (setq matches (cons + (append + (or + ;; This page already is a match. + (car matches) + ;; This is the first match on page. + (list page)) + (list (buffer-substring + (line-beginning-position) + (line-end-position)))) + (cdr matches)))) + (setq lastpage page))) + (nreverse matches)))) + +(defun doc-view-search-no-of-matches (list) + "Extract the number of matches from the search result LIST." + (let ((no 0)) + (dolist (p list) + (setq no (+ no (1- (length p))))) + no)) + +(defun doc-view-search () + "Query for a regexp and search the current document. +If the current document hasn't been transformed to plain text +till now do that first. You should try searching anew when the +conversion finished." + (interactive) + ;; New search, so forget the old results. + (setq doc-view-current-search-matches nil) + (let ((txt (concat (doc-view-file-name-to-directory-name + doc-view-current-doc) + "doc.txt"))) + (if (file-readable-p txt) + (progn + (setq doc-view-current-search-matches + (doc-view-search-internal + (read-from-minibuffer "Regexp: ") + txt)) + (message "DocView: search yielded %d matches." + (doc-view-search-no-of-matches + doc-view-current-search-matches))) + ;; We must convert to TXT first! + (if doc-view-current-converter-process + (message "DocView: please wait till conversion finished.") + (let ((ext (file-name-extension doc-view-current-doc))) + (cond + ((string= ext "pdf") + ;; Doc is a PDF, so convert it to TXT + (doc-view-pdf->txt doc-view-current-doc txt)) + ((string= ext "ps") + ;; Doc is a PS, so convert it to PDF (which will be converted to + ;; TXT thereafter). + (doc-view-ps->pdf doc-view-current-doc + (concat (doc-view-file-name-to-directory-name + doc-view-current-doc) + "doc.pdf"))) + ((string= ext "dvi") + ;; Doc is a DVI. This means that a doc.pdf already exists in its + ;; cache subdirectory. + (doc-view-pdf->txt (concat (doc-view-file-name-to-directory-name + doc-view-current-doc) + "doc.pdf") + txt)) + (t (error "DocView doesn't know what to do")))))))) + +(defun doc-view-search-next-match (arg) + "Go to the ARGth next matching page." + (interactive "p") + (let* ((next-pages (remove-if (lambda (i) (<= (car i) doc-view-current-page)) + doc-view-current-search-matches)) + (page (car (nth (1- arg) next-pages)))) + (if page + (doc-view-goto-page page) + (when (and + doc-view-current-search-matches + (y-or-n-p "No more matches after current page. Wrap to first match? ")) + (doc-view-goto-page (caar doc-view-current-search-matches)))))) + +(defun doc-view-search-previous-match (arg) + "Go to the ARGth previous matching page." + (interactive "p") + (let* ((prev-pages (remove-if (lambda (i) (>= (car i) doc-view-current-page)) + doc-view-current-search-matches)) + (page (car (nth (1- arg) (nreverse prev-pages))))) + (if page + (doc-view-goto-page page) + (when (and + doc-view-current-search-matches + (y-or-n-p "No more matches before current page. Wrap to last match? ")) + (doc-view-goto-page (caar (last doc-view-current-search-matches))))))) + +;;;; User Interface Commands + +(defun doc-view (no-cache &optional file) + "Convert FILE to png and start viewing it. +If no FILE is given, query for on. +If this FILE is still in the cache, don't convert and use the +existing page files. With prefix arg NO-CACHE, don't use the +cached files and convert anew." + (interactive "P") + (if (not (and (image-type-available-p 'png) + (display-images-p))) + (message "DocView: your emacs or display doesn't support png images.") + (let* ((doc (or file + (expand-file-name (read-file-name "File: " nil nil t)))) + (buffer (get-buffer-create (format "*DocView: %s*" doc))) + (dir (doc-view-file-name-to-directory-name doc))) + (switch-to-buffer buffer) + (doc-view-buffer-message) + (doc-view-mode) + (setq doc-view-current-doc doc) + (setq doc-view-current-page 1) + (if (not (and (file-exists-p dir) + (not no-cache))) + (progn + (setq doc-view-current-cache-dir nil) + (doc-view-convert-doc doc-view-current-doc)) + (message "DocView: using cached files!") + (doc-view-display doc-view-current-doc))))) + +(defun doc-view-dired (no-cache) + "View the current dired file with doc-view. +NO-CACHE is the same as in `doc-view'. + +You might want to bind this command to a dired key, e.g. + + (define-key dired-mode-map (kbd \"C-c d\") 'doc-view-dired)" + (interactive "P") + (doc-view no-cache (dired-get-file-for-visit))) + +(defun doc-view-clear-cache () + "Delete the whole cache (`doc-view-cache-directory')." + (interactive) + (dired-delete-file doc-view-cache-directory 'always) + (make-directory doc-view-cache-directory)) + +(defun doc-view-dired-cache () + "Open `dired' in `doc-view-cache-directory'." + (interactive) + (dired doc-view-cache-directory)) + +(provide 'doc-view) + +;; Local Variables: +;; mode: outline-minor +;; End: + +;;; doc-view.el ends here