Add 2007 to copyright years.
[bpt/emacs.git] / lisp / doc-view.el
CommitLineData
94dbe99c
TTN
1;;; doc-view.el --- View PDF/PostScript/DVI files in Emacs
2
3;; Copyright (C) 2007 Free Software Foundation, Inc.
4;;
5;; Author: Tassilo Horn <tassilo@member.fsf.org>
6;; Maintainer: Tassilo Horn <tassilo@member.fsf.org>
7;; Keywords: files, pdf, ps, dvi
94dbe99c
TTN
8
9;; This file is part of GNU Emacs.
10
11;; GNU Emacs is free software; you can redistribute it and/or modify
12;; it under the terms of the GNU General Public License as published by
13;; the Free Software Foundation; either version 3, or (at your option)
14;; any later version.
15
16;; GNU Emacs is distributed in the hope that it will be useful,
17;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19;; GNU General Public License for more details.
20
21;; You should have received a copy of the GNU General Public License
22;; along with GNU Emacs; see the file COPYING. If not, write to the
23;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
24;; Boston, MA 02110-1301, USA.
25
26;;; Requirements:
27
4b378e75
RS
28;; doc-view.el requires GNU Emacs 22.1 or newer. You also need Ghostscript,
29;; `dvipdfm' which comes with teTeX and `pdftotext', which comes with xpdf
640602f7 30;; (http://www.foolabs.com/xpdf/) or poppler (http://poppler.freedesktop.org/).
94dbe99c
TTN
31
32;;; Commentary:
33
34;; DocView is a document viewer for Emacs. It converts PDF, PS and DVI files
35;; to a set of PNG files, one PNG for each page, and displays the PNG images
36;; inside an Emacs buffer. This buffer uses `doc-view-mode' which provides
37;; convenient key bindings for browsing the document.
38;;
640602f7 39;; To use it simply open a document file with
94dbe99c 40;;
640602f7 41;; C-x C-f ~/path/to/document RET
94dbe99c 42;;
640602f7
RS
43;; and the document will be converted and displayed, if your emacs supports png
44;; images. With `C-c C-c' you can toggle between the rendered images
45;; representation and the source text representation of the document. With
46;; `C-c C-e' you can switch to an appropriate editing mode for the document.
94dbe99c
TTN
47;;
48;; Since conversion may take some time all the PNG images are cached in a
49;; subdirectory of `doc-view-cache-directory' and reused when you want to view
640602f7
RS
50;; that file again. To reconvert a document hit `g' (`doc-view-reconvert-doc')
51;; when displaying the document. To delete all cached files use
94dbe99c
TTN
52;; `doc-view-clear-cache'. To open the cache with dired, so that you can tidy
53;; it out use `doc-view-dired-cache'.
54;;
55;; When conversion in underway the first page will be displayed as soon as it
56;; is available and the available pages are refreshed every
57;; `doc-view-conversion-refresh-interval' seconds. If that variable is nil the
58;; pages won't be displayed before conversion of the document finished
59;; completely.
60;;
61;; DocView lets you select a slice of the displayed pages. This slice will be
62;; remembered and applied to all pages of the current document. This enables
63;; you to cut away the margins of a document to save some space. To select a
64;; slice you can use `doc-view-set-slice' (bound to `s s') which will query you
65;; for the coordinates of the slice's top-left corner and its width and height.
66;; A much more convenient way to do the same is offered by the command
67;; `doc-view-set-slice-using-mouse' (bound to `s m'). After invokation you
68;; only have to press mouse-1 at the top-left corner and drag it to the
69;; bottom-right corner of the desired slice. To reset the slice use
70;; `doc-view-reset-slice' (bound to `s r').
71;;
94dbe99c
TTN
72;; You can also search within the document. The command `doc-view-search'
73;; (bound to `C-s') queries for a search regexp and initializes a list of all
74;; matching pages and messages how many match-pages were found. After that you
75;; can jump to the next page containing a match with
76;; `doc-view-search-next-match' (bound to `C-S-n') or to the previous matching
77;; page with `doc-view-search-previous-match' (bound to `C-S-p'). This works
78;; by searching a plain text representation of the document. If that doesn't
79;; already exist the first invokation of `doc-view-search' starts the
80;; conversion. When that finishes and you're still viewing the document
81;; (i.e. you didn't switch to another buffer) you're queried for the regexp
82;; then.
640602f7
RS
83;;
84;; Dired users can simply hit `v' on a document file. If it's a PS, PDF or DVI
85;; it will be opened using `doc-view-mode'.
86;;
94dbe99c
TTN
87
88;;; Configuration:
89
640602f7
RS
90;; If the images are too small or too big you should set the "-rXXX" option in
91;; `doc-view-ghostscript-options' to another value. (The bigger your screen,
92;; the higher the value.)
94dbe99c
TTN
93;;
94;; This and all other options can be set with the customization interface.
95;; Simply do
96;;
97;; M-x customize-group RET doc-view RET
98;;
99;; and modify them to your needs.
100
101;;; Code:
102
103(require 'dired)
414dd971 104(require 'image-mode)
94dbe99c
TTN
105(eval-when-compile (require 'cl))
106
107;;;; Customization Options
108
109(defgroup doc-view nil
110 "In-buffer viewer for PDF, PostScript and DVI files."
111 :link '(function-link doc-view)
112 :version "22.2"
113 :group 'applications
114 :group 'multimedia
115 :prefix "doc-view-")
116
c9a9a5e3 117(defcustom doc-view-ghostscript-program (executable-find "gs")
94dbe99c 118 "Program to convert PS and PDF files to PNG."
c9a9a5e3 119 :type 'file
94dbe99c
TTN
120 :group 'doc-view)
121
122(defcustom doc-view-ghostscript-options
6a658a30
RS
123 '("-dSAFER" ;; Avoid security problems when rendering files from untrusted
124 ;; sources.
125 "-dNOPAUSE" "-sDEVICE=png16m" "-dTextAlphaBits=4"
126 "-dBATCH" "-dGraphicsAlphaBits=4" "-dQUIET" "-r100")
4b378e75 127 "A list of options to give to ghostscript."
c9a9a5e3 128 :type '(repeat string)
94dbe99c
TTN
129 :group 'doc-view)
130
c9a9a5e3 131(defcustom doc-view-dvipdfm-program (executable-find "dvipdfm")
94dbe99c
TTN
132 "Program to convert DVI files to PDF.
133
134DVI file will be converted to PDF before the resulting PDF is
135converted to PNG."
c9a9a5e3 136 :type 'file
94dbe99c
TTN
137 :group 'doc-view)
138
c9a9a5e3 139(defcustom doc-view-ps2pdf-program (executable-find "ps2pdf")
94dbe99c
TTN
140 "Program to convert PS files to PDF.
141
142PS files will be converted to PDF before searching is possible."
c9a9a5e3 143 :type 'file
94dbe99c
TTN
144 :group 'doc-view)
145
c9a9a5e3 146(defcustom doc-view-pdftotext-program (executable-find "pdftotext")
94dbe99c
TTN
147 "Program to convert PDF files to plain text.
148
149Needed for searching."
c9a9a5e3 150 :type 'file
94dbe99c
TTN
151 :group 'doc-view)
152
153(defcustom doc-view-cache-directory (concat temporary-file-directory
1ca678aa 154 "doc-view")
94dbe99c 155 "The base directory, where the PNG images will be saved."
c9a9a5e3 156 :type 'directory
94dbe99c
TTN
157 :group 'doc-view)
158
159(defcustom doc-view-conversion-buffer "*doc-view conversion output*"
160 "The buffer where messages from the converter programs go to."
c9a9a5e3 161 :type 'string
94dbe99c
TTN
162 :group 'doc-view)
163
164(defcustom doc-view-conversion-refresh-interval 3
165 "Every how much seconds the DocView buffer gets refreshed while conversion.
166After such an refresh newly converted pages will be available for
167viewing. If set to nil there won't be any refreshes and the
168pages won't be displayed before conversion of the whole document
169has finished."
c9a9a5e3 170 :type 'integer
94dbe99c
TTN
171 :group 'doc-view)
172
173;;;; Internal Variables
174
175(defvar doc-view-current-files nil
176 "Only used internally.")
177
178(defvar doc-view-current-page nil
179 "Only used internally.")
180
181(defvar doc-view-current-doc nil
182 "Only used internally.")
183
184(defvar doc-view-current-converter-process nil
185 "Only used internally.")
186
187(defvar doc-view-current-timer nil
188 "Only used internally.")
189
190(defvar doc-view-current-slice nil
191 "Only used internally.")
192
193(defvar doc-view-current-cache-dir nil
194 "Only used internally.")
195
196(defvar doc-view-current-search-matches nil
197 "Only used internally.")
198
199(defvar doc-view-current-image nil
200 "Only used internally.")
201
202(defvar doc-view-current-info nil
203 "Only used internally.")
204
640602f7
RS
205(defvar doc-view-current-display nil
206 "Only used internally.")
207
208;;;; DocView Keymaps
94dbe99c
TTN
209
210(defvar doc-view-mode-map
211 (let ((map (make-sparse-keymap)))
212 ;; Navigation in the document
213 (define-key map (kbd "n") 'doc-view-next-page)
214 (define-key map (kbd "p") 'doc-view-previous-page)
eb8d0216
SM
215 (define-key map (kbd "<next>") 'forward-page)
216 (define-key map (kbd "<prior>") 'backward-page)
217 (define-key map [remap forward-page] 'doc-view-next-page)
218 (define-key map [remap backward-page] 'doc-view-previous-page)
94dbe99c
TTN
219 (define-key map (kbd "SPC") 'doc-view-scroll-up-or-next-page)
220 (define-key map (kbd "DEL") 'doc-view-scroll-down-or-previous-page)
221 (define-key map (kbd "M-<") 'doc-view-first-page)
222 (define-key map (kbd "M->") 'doc-view-last-page)
223 (define-key map (kbd "g") 'doc-view-goto-page)
224 ;; Killing/burying the buffer (and the process)
225 (define-key map (kbd "q") 'bury-buffer)
226 (define-key map (kbd "k") 'doc-view-kill-proc-and-buffer)
94dbe99c
TTN
227 ;; Slicing the image
228 (define-key map (kbd "s s") 'doc-view-set-slice)
229 (define-key map (kbd "s m") 'doc-view-set-slice-using-mouse)
230 (define-key map (kbd "s r") 'doc-view-reset-slice)
231 ;; Searching
232 (define-key map (kbd "C-s") 'doc-view-search)
233 (define-key map (kbd "<find>") 'doc-view-search)
234 (define-key map (kbd "C-S-n") 'doc-view-search-next-match)
235 (define-key map (kbd "C-S-p") 'doc-view-search-previous-match)
236 ;; Scrolling
eb8d0216
SM
237 (define-key map [remap forward-char] 'image-forward-hscroll)
238 (define-key map [remap backward-char] 'image-backward-hscroll)
239 (define-key map [remap next-line] 'image-next-line)
240 (define-key map [remap previous-line] 'image-previous-line)
94dbe99c
TTN
241 ;; Show the tooltip
242 (define-key map (kbd "C-t") 'doc-view-show-tooltip)
640602f7
RS
243 ;; Toggle between text and image display or editing
244 (define-key map (kbd "C-c C-c") 'doc-view-toggle-display)
245 (define-key map (kbd "C-c C-e") 'doc-view-edit-doc)
246 ;; Reconvert the current document
247 (define-key map (kbd "g") 'doc-view-reconvert-doc)
94dbe99c
TTN
248 (suppress-keymap map)
249 map)
640602f7
RS
250 "Keymap used by `doc-view-mode' when displaying a doc as a set of images.")
251
252(defvar doc-view-mode-text-map
253 (let ((map (make-sparse-keymap)))
254 ;; Toggle between text and image display or editing
255 (define-key map (kbd "C-c C-c") 'doc-view-toggle-display)
256 (define-key map (kbd "C-c C-e") 'doc-view-edit-doc)
257 ;; Killing/burying the buffer (and the process)
258 (define-key map (kbd "q") 'bury-buffer)
259 (define-key map (kbd "k") 'doc-view-kill-proc-and-buffer)
260 (define-key map (kbd "C-x k") 'doc-view-kill-proc-and-buffer)
261 map)
262 "Keymap used by `doc-view-mode' when displaying a document as text.")
94dbe99c
TTN
263
264;;;; Navigation Commands
265
266(defun doc-view-goto-page (page)
267 "View the page given by PAGE."
268 (interactive "nPage: ")
269 (let ((len (length doc-view-current-files)))
270 (if (< page 1)
1ca678aa 271 (setq page 1)
94dbe99c 272 (when (> page len)
1ca678aa 273 (setq page len)))
94dbe99c 274 (setq doc-view-current-page page
1ca678aa
MC
275 doc-view-current-info
276 (concat
277 (propertize
278 (format "Page %d of %d."
279 doc-view-current-page
280 len) 'face 'bold)
281 ;; Tell user if converting isn't finished yet
282 (if doc-view-current-converter-process
283 " (still converting...)\n"
284 "\n")
285 ;; Display context infos if this page matches the last search
286 (when (and doc-view-current-search-matches
287 (assq doc-view-current-page
288 doc-view-current-search-matches))
289 (concat (propertize "Search matches:\n" 'face 'bold)
290 (let ((contexts ""))
291 (dolist (m (cdr (assq doc-view-current-page
292 doc-view-current-search-matches)))
293 (setq contexts (concat contexts " - \"" m "\"\n")))
294 contexts)))))
94dbe99c 295 ;; Update the buffer
640602f7
RS
296 (let ((inhibit-read-only t))
297 (erase-buffer)
298 (let ((beg (point)))
299 (doc-view-insert-image (nth (1- page) doc-view-current-files)
300 :pointer 'arrow)
301 (put-text-property beg (point) 'help-echo doc-view-current-info))
302 (insert "\n" doc-view-current-info)
303 (goto-char (point-min))
304 (forward-char))
305 (set-buffer-modified-p nil)))
94dbe99c
TTN
306
307(defun doc-view-next-page (&optional arg)
308 "Browse ARG pages forward."
309 (interactive "p")
310 (doc-view-goto-page (+ doc-view-current-page (or arg 1))))
311
312(defun doc-view-previous-page (&optional arg)
313 "Browse ARG pages backward."
314 (interactive "p")
315 (doc-view-goto-page (- doc-view-current-page (or arg 1))))
316
317(defun doc-view-first-page ()
318 "View the first page."
319 (interactive)
320 (doc-view-goto-page 1))
321
322(defun doc-view-last-page ()
323 "View the last page."
324 (interactive)
325 (doc-view-goto-page (length doc-view-current-files)))
326
327(defun doc-view-scroll-up-or-next-page ()
328 "Scroll page up if possible, else goto next page."
329 (interactive)
330 (condition-case nil
331 (scroll-up)
332 (error (doc-view-next-page))))
333
334(defun doc-view-scroll-down-or-previous-page ()
335 "Scroll page down if possible, else goto previous page."
336 (interactive)
337 (condition-case nil
338 (scroll-down)
339 (error (doc-view-previous-page)
1ca678aa 340 (goto-char (point-max)))))
94dbe99c 341
640602f7
RS
342(defun doc-view-kill-proc ()
343 "Kill the current converter process."
344 (interactive)
345 (when doc-view-current-converter-process
346 (kill-process doc-view-current-converter-process))
347 (when doc-view-current-timer
348 (cancel-timer doc-view-current-timer)
349 (setq doc-view-current-timer nil))
350 (setq mode-line-process nil))
351
94dbe99c
TTN
352(defun doc-view-kill-proc-and-buffer ()
353 "Kill the current converter process and buffer."
354 (interactive)
640602f7 355 (doc-view-kill-proc)
94dbe99c 356 (when (eq major-mode 'doc-view-mode)
94dbe99c
TTN
357 (kill-buffer (current-buffer))))
358
359;;;; Conversion Functions
360
640602f7
RS
361(defun doc-view-reconvert-doc (&rest args)
362 "Reconvert the current document.
363Should be invoked when the cached images aren't up-to-date."
364 (interactive)
365 (let ((inhibit-read-only t)
366 (doc doc-view-current-doc))
367 (doc-view-kill-proc)
368 ;; Clear the old cached files
369 (when (file-exists-p (doc-view-current-cache-dir))
370 (dired-delete-file (doc-view-current-cache-dir) 'always))
371 (doc-view-kill-proc-and-buffer)
372 (find-file doc)))
373
374(defun doc-view-current-cache-dir ()
375 "Return the directory where the png files of the current doc should be saved.
376It's a subdirectory of `doc-view-cache-directory'."
94dbe99c
TTN
377 (if doc-view-current-cache-dir
378 doc-view-current-cache-dir
640602f7
RS
379 (setq doc-view-current-cache-dir
380 (file-name-as-directory
381 (concat (file-name-as-directory doc-view-cache-directory)
382 (let ((doc doc-view-current-doc))
383 (with-temp-buffer
384 (insert-file-contents-literally doc)
385 (md5 (current-buffer)))))))))
94dbe99c
TTN
386
387(defun doc-view-dvi->pdf-sentinel (proc event)
e48a5bf9 388 "If DVI->PDF conversion was successful, convert the PDF to PNG now."
94dbe99c
TTN
389 (if (not (string-match "finished" event))
390 (message "DocView: dvi->pdf process changed status to %s." event)
391 (set-buffer (process-get proc 'buffer))
640602f7
RS
392 (setq doc-view-current-converter-process nil
393 mode-line-process nil)
94dbe99c
TTN
394 ;; Now go on converting this PDF to a set of PNG files.
395 (let* ((pdf (process-get proc 'pdf-file))
640602f7 396 (png (concat (doc-view-current-cache-dir)
1ca678aa 397 "page-%d.png")))
94dbe99c
TTN
398 (doc-view-pdf/ps->png pdf png))))
399
400(defun doc-view-dvi->pdf (dvi pdf)
401 "Convert DVI to PDF asynchrounously."
94dbe99c 402 (setq doc-view-current-converter-process
640602f7 403 (start-process "dvi->pdf" doc-view-conversion-buffer
1ca678aa 404 doc-view-dvipdfm-program
640602f7
RS
405 "-o" pdf dvi)
406 mode-line-process (list (format ":%s" doc-view-current-converter-process)))
94dbe99c 407 (set-process-sentinel doc-view-current-converter-process
1ca678aa 408 'doc-view-dvi->pdf-sentinel)
94dbe99c
TTN
409 (process-put doc-view-current-converter-process 'buffer (current-buffer))
410 (process-put doc-view-current-converter-process 'pdf-file pdf))
411
412(defun doc-view-pdf/ps->png-sentinel (proc event)
413 "If PDF/PS->PNG conversion was successful, update the display."
414 (if (not (string-match "finished" event))
415 (message "DocView: converter process changed status to %s." event)
416 (set-buffer (process-get proc 'buffer))
640602f7
RS
417 (setq doc-view-current-converter-process nil
418 mode-line-process nil)
94dbe99c
TTN
419 (when doc-view-current-timer
420 (cancel-timer doc-view-current-timer)
421 (setq doc-view-current-timer nil))
94dbe99c
TTN
422 ;; Yippie, finished. Update the display!
423 (doc-view-display doc-view-current-doc)))
424
425(defun doc-view-pdf/ps->png (pdf-ps png)
426 "Convert PDF-PS to PNG asynchrounously."
94dbe99c 427 (setq doc-view-current-converter-process
1ca678aa 428 (apply 'start-process
640602f7 429 (append (list "pdf/ps->png" doc-view-conversion-buffer
1ca678aa
MC
430 doc-view-ghostscript-program)
431 doc-view-ghostscript-options
432 (list (concat "-sOutputFile=" png))
640602f7
RS
433 (list pdf-ps)))
434 mode-line-process (list (format ":%s" doc-view-current-converter-process)))
94dbe99c 435 (process-put doc-view-current-converter-process
1ca678aa 436 'buffer (current-buffer))
94dbe99c 437 (set-process-sentinel doc-view-current-converter-process
1ca678aa 438 'doc-view-pdf/ps->png-sentinel)
94dbe99c
TTN
439 (when doc-view-conversion-refresh-interval
440 (setq doc-view-current-timer
1ca678aa 441 (run-at-time "1 secs" doc-view-conversion-refresh-interval
640602f7 442 'doc-view-display-maybe
1ca678aa 443 doc-view-current-doc))))
94dbe99c
TTN
444
445(defun doc-view-pdf->txt-sentinel (proc event)
446 (if (not (string-match "finished" event))
447 (message "DocView: converter process changed status to %s." event)
448 (let ((current-buffer (current-buffer))
1ca678aa 449 (proc-buffer (process-get proc 'buffer)))
94dbe99c 450 (set-buffer proc-buffer)
640602f7
RS
451 (setq doc-view-current-converter-process nil
452 mode-line-process nil)
94dbe99c
TTN
453 ;; If the user looks at the DocView buffer where the conversion was
454 ;; performed, search anew. This time it will be queried for a regexp.
455 (when (eq current-buffer proc-buffer)
1ca678aa 456 (doc-view-search)))))
94dbe99c
TTN
457
458(defun doc-view-pdf->txt (pdf txt)
459 "Convert PDF to TXT asynchrounously."
94dbe99c 460 (setq doc-view-current-converter-process
640602f7 461 (start-process "pdf->txt" doc-view-conversion-buffer
1ca678aa 462 doc-view-pdftotext-program "-raw"
640602f7
RS
463 pdf txt)
464 mode-line-process (list (format ":%s" doc-view-current-converter-process)))
94dbe99c 465 (set-process-sentinel doc-view-current-converter-process
1ca678aa 466 'doc-view-pdf->txt-sentinel)
94dbe99c
TTN
467 (process-put doc-view-current-converter-process 'buffer (current-buffer)))
468
469(defun doc-view-ps->pdf-sentinel (proc event)
470 (if (not (string-match "finished" event))
471 (message "DocView: converter process changed status to %s." event)
472 (set-buffer (process-get proc 'buffer))
640602f7
RS
473 (setq doc-view-current-converter-process nil
474 mode-line-process nil)
94dbe99c
TTN
475 ;; Now we can transform to plain text.
476 (doc-view-pdf->txt (process-get proc 'pdf-file)
640602f7 477 (concat (doc-view-current-cache-dir)
1ca678aa 478 "doc.txt"))))
94dbe99c
TTN
479
480(defun doc-view-ps->pdf (ps pdf)
481 "Convert PS to PDF asynchronously."
94dbe99c 482 (setq doc-view-current-converter-process
640602f7 483 (start-process "ps->pdf" doc-view-conversion-buffer
1ca678aa 484 doc-view-ps2pdf-program
6a658a30
RS
485 ps pdf
486 ;; Avoid security problems when rendering files from
487 ;; untrusted sources.
488 "-dSAFER")
640602f7 489 mode-line-process (list (format ":%s" doc-view-current-converter-process)))
94dbe99c 490 (set-process-sentinel doc-view-current-converter-process
1ca678aa 491 'doc-view-ps->pdf-sentinel)
94dbe99c
TTN
492 (process-put doc-view-current-converter-process 'buffer (current-buffer))
493 (process-put doc-view-current-converter-process 'pdf-file pdf))
494
640602f7
RS
495(defun doc-view-convert-current-doc ()
496 "Convert `doc-view-current-doc' to a set of png files, one file per page.
497Those files are saved in the directory given by the function
498`doc-view-current-cache-dir'."
94dbe99c 499 (clear-image-cache)
640602f7
RS
500 (let ((png-file (concat (doc-view-current-cache-dir)
501 "page-%d.png")))
502 (make-directory doc-view-current-cache-dir t)
503 (if (not (string= (file-name-extension doc-view-current-doc) "dvi"))
1ca678aa 504 ;; Convert to PNG images.
640602f7 505 (doc-view-pdf/ps->png doc-view-current-doc png-file)
4b378e75 506 ;; DVI files have to be converted to PDF before Ghostscript can process
94dbe99c 507 ;; it.
640602f7
RS
508 (doc-view-dvi->pdf doc-view-current-doc
509 (concat (file-name-as-directory doc-view-current-cache-dir)
1ca678aa 510 "doc.pdf")))))
94dbe99c 511
94dbe99c
TTN
512;;;; Slicing
513
514(defun doc-view-set-slice (x y width height)
515 "Set the slice of the images that should be displayed.
516You can use this function to tell doc-view not to display the
517margins of the document. It prompts for the top-left corner (X
518and Y) of the slice to display and its WIDTH and HEIGHT.
519
520See `doc-view-set-slice-using-mouse' for a more convenient way to
521do that. To reset the slice use `doc-view-reset-slice'."
522 (interactive
523 (let* ((size (image-size doc-view-current-image t))
1ca678aa
MC
524 (a (read-number (format "Top-left X (0..%d): " (car size))))
525 (b (read-number (format "Top-left Y (0..%d): " (cdr size))))
526 (c (read-number (format "Width (0..%d): " (- (car size) a))))
527 (d (read-number (format "Height (0..%d): " (- (cdr size) b)))))
94dbe99c
TTN
528 (list a b c d)))
529 (setq doc-view-current-slice (list x y width height))
530 ;; Redisplay
531 (doc-view-goto-page doc-view-current-page))
532
533(defun doc-view-set-slice-using-mouse ()
534 "Set the slice of the images that should be displayed.
535You set the slice by pressing mouse-1 at its top-left corner and
536dragging it to its bottom-right corner. See also
537`doc-view-set-slice' and `doc-view-reset-slice'."
538 (interactive)
539 (let (x y w h done)
540 (while (not done)
541 (let ((e (read-event
1ca678aa
MC
542 (concat "Press mouse-1 at the top-left corner and "
543 "drag it to the bottom-right corner!"))))
544 (when (eq (car e) 'drag-mouse-1)
545 (setq x (car (posn-object-x-y (event-start e))))
546 (setq y (cdr (posn-object-x-y (event-start e))))
547 (setq w (- (car (posn-object-x-y (event-end e))) x))
548 (setq h (- (cdr (posn-object-x-y (event-end e))) y))
549 (setq done t))))
94dbe99c
TTN
550 (doc-view-set-slice x y w h)))
551
552(defun doc-view-reset-slice ()
e48a5bf9 553 "Reset the current slice.
94dbe99c
TTN
554After calling this function the whole pages will be visible
555again."
556 (interactive)
557 (setq doc-view-current-slice nil)
558 ;; Redisplay
559 (doc-view-goto-page doc-view-current-page))
560
561;;;; Display
562
563(defun doc-view-insert-image (file &rest args)
564 "Insert the given png FILE.
e48a5bf9 565ARGS is a list of image descriptors."
94dbe99c
TTN
566 (let ((image (apply 'create-image file 'png nil args)))
567 (setq doc-view-current-image image)
568 (insert-image image (concat "[" file "]") nil doc-view-current-slice)))
569
570(defun doc-view-sort (a b)
571 "Return non-nil if A should be sorted before B.
572Predicate for sorting `doc-view-current-files'."
573 (if (< (length a) (length b))
574 t
575 (if (> (length a) (length b))
1ca678aa 576 nil
94dbe99c
TTN
577 (string< a b))))
578
640602f7
RS
579(defun doc-view-display-maybe (doc)
580 "Call `doc-view-display' iff we're in the image display."
581 (when (eq doc-view-current-display 'image)
582 (doc-view-display doc)))
583
94dbe99c
TTN
584(defun doc-view-display (doc)
585 "Start viewing the document DOC."
640602f7
RS
586 (set-buffer (get-file-buffer doc))
587 (setq doc-view-current-files
588 (sort (directory-files (doc-view-current-cache-dir) t
589 "page-[0-9]+\\.png" t)
590 'doc-view-sort))
591 (when (> (length doc-view-current-files) 0)
592 (doc-view-goto-page doc-view-current-page)))
94dbe99c
TTN
593
594(defun doc-view-buffer-message ()
94dbe99c 595 (insert (propertize "Welcome to DocView!" 'face 'bold)
1ca678aa
MC
596 "\n"
597 "
94dbe99c
TTN
598If you see this buffer it means that the document you want to
599view gets converted to PNG now and the conversion of the first
600page hasn't finished yet or
601`doc-view-conversion-refresh-interval' is set to nil.
602
603For now these keys are useful:
604
1ca678aa
MC
605`q' : Bury this buffer. Conversion will go on in background.
606`k' : Kill the conversion process and this buffer.\n")
640602f7 607 (set-buffer-modified-p nil))
94dbe99c
TTN
608
609(defun doc-view-show-tooltip ()
610 (interactive)
611 (tooltip-show doc-view-current-info))
612
640602f7
RS
613;;;;; Toggle between text and image display
614
615(defun doc-view-toggle-display ()
616 "Start or stop displaying a document file as a set of images.
617This command toggles between showing the text of the document
618file and showing the document as a set of images."
619 (interactive)
620 (if (get-text-property (point-min) 'display)
621 ;; Switch to text display
622 (let ((inhibit-read-only t))
623 (erase-buffer)
624 (insert-file-contents doc-view-current-doc)
625 (use-local-map doc-view-mode-text-map)
626 (setq mode-name "DocView[text]"
627 doc-view-current-display 'text)
628 (if (called-interactively-p)
629 (message "Repeat this command to go back to displaying the file as images")))
630 ;; Switch to image display
631 (let ((inhibit-read-only t))
632 (erase-buffer)
633 (doc-view-buffer-message)
634 (setq doc-view-current-page (or doc-view-current-page 1))
635 (if (file-exists-p (doc-view-current-cache-dir))
636 (progn
637 (message "DocView: using cached files!")
638 (doc-view-display doc-view-current-doc))
639 (doc-view-convert-current-doc))
640 (use-local-map doc-view-mode-map)
641 (setq mode-name (format "DocView")
642 doc-view-current-display 'image)
643 (if (called-interactively-p)
644 (message "Repeat this command to go back to displaying the file as text"))))
645 (set-buffer-modified-p nil))
646
647;;;;; Leave doc-view-mode and open the file for edit
648
649(defun doc-view-edit-doc ()
650 "Leave `doc-view-mode' and open the current doc with an appropriate editing mode."
651 (interactive)
652 (let ((filename doc-view-current-doc)
653 (auto-mode-alist (append '(("\\.[eE]?[pP][sS]\\'" . ps-mode)
654 ("\\.\\(pdf\\|PDF\\|dvi\\|DVI\\)$" . fundamental-mode))
655 auto-mode-alist)))
656 (kill-buffer (current-buffer))
657 (find-file filename)))
658
94dbe99c
TTN
659;;;; Searching
660
661(defun doc-view-search-internal (regexp file)
662 "Return a list of FILE's pages that contain text matching REGEXP.
1ca678aa
MC
663The value is an alist of the form (PAGE CONTEXTS) where PAGE is
664the pagenumber and CONTEXTS are all lines of text containing a match."
94dbe99c
TTN
665 (with-temp-buffer
666 (insert-file-contents file)
667 (let ((page 1)
1ca678aa
MC
668 (lastpage 1)
669 matches)
94dbe99c 670 (while (re-search-forward (concat "\\(?:\\([\f]\\)\\|\\("
1ca678aa
MC
671 regexp "\\)\\)") nil t)
672 (when (match-string 1) (incf page))
673 (when (match-string 2)
674 (if (/= page lastpage)
675 (setq matches (push (cons page
676 (list (buffer-substring
677 (line-beginning-position)
678 (line-end-position))))
679 matches))
680 (setq matches (cons
681 (append
682 (or
683 ;; This page already is a match.
684 (car matches)
685 ;; This is the first match on page.
686 (list page))
687 (list (buffer-substring
688 (line-beginning-position)
689 (line-end-position))))
690 (cdr matches))))
691 (setq lastpage page)))
94dbe99c
TTN
692 (nreverse matches))))
693
694(defun doc-view-search-no-of-matches (list)
695 "Extract the number of matches from the search result LIST."
696 (let ((no 0))
697 (dolist (p list)
698 (setq no (+ no (1- (length p)))))
699 no))
700
701(defun doc-view-search ()
702 "Query for a regexp and search the current document.
703If the current document hasn't been transformed to plain text
704till now do that first. You should try searching anew when the
705conversion finished."
706 (interactive)
707 ;; New search, so forget the old results.
708 (setq doc-view-current-search-matches nil)
640602f7 709 (let ((txt (concat (doc-view-current-cache-dir)
1ca678aa 710 "doc.txt")))
94dbe99c 711 (if (file-readable-p txt)
1ca678aa
MC
712 (progn
713 (setq doc-view-current-search-matches
714 (doc-view-search-internal
715 (read-from-minibuffer "Regexp: ")
716 txt))
717 (message "DocView: search yielded %d matches."
718 (doc-view-search-no-of-matches
719 doc-view-current-search-matches)))
94dbe99c
TTN
720 ;; We must convert to TXT first!
721 (if doc-view-current-converter-process
1ca678aa
MC
722 (message "DocView: please wait till conversion finished.")
723 (let ((ext (file-name-extension doc-view-current-doc)))
724 (cond
725 ((string= ext "pdf")
726 ;; Doc is a PDF, so convert it to TXT
727 (doc-view-pdf->txt doc-view-current-doc txt))
728 ((string= ext "ps")
729 ;; Doc is a PS, so convert it to PDF (which will be converted to
730 ;; TXT thereafter).
731 (doc-view-ps->pdf doc-view-current-doc
640602f7 732 (concat (doc-view-current-cache-dir)
1ca678aa
MC
733 "doc.pdf")))
734 ((string= ext "dvi")
735 ;; Doc is a DVI. This means that a doc.pdf already exists in its
736 ;; cache subdirectory.
640602f7 737 (doc-view-pdf->txt (concat (doc-view-current-cache-dir)
1ca678aa
MC
738 "doc.pdf")
739 txt))
740 (t (error "DocView doesn't know what to do"))))))))
94dbe99c
TTN
741
742(defun doc-view-search-next-match (arg)
743 "Go to the ARGth next matching page."
744 (interactive "p")
745 (let* ((next-pages (remove-if (lambda (i) (<= (car i) doc-view-current-page))
1ca678aa
MC
746 doc-view-current-search-matches))
747 (page (car (nth (1- arg) next-pages))))
94dbe99c 748 (if page
1ca678aa 749 (doc-view-goto-page page)
94dbe99c 750 (when (and
1ca678aa
MC
751 doc-view-current-search-matches
752 (y-or-n-p "No more matches after current page. Wrap to first match? "))
753 (doc-view-goto-page (caar doc-view-current-search-matches))))))
94dbe99c
TTN
754
755(defun doc-view-search-previous-match (arg)
756 "Go to the ARGth previous matching page."
757 (interactive "p")
758 (let* ((prev-pages (remove-if (lambda (i) (>= (car i) doc-view-current-page))
1ca678aa
MC
759 doc-view-current-search-matches))
760 (page (car (nth (1- arg) (nreverse prev-pages)))))
94dbe99c 761 (if page
1ca678aa 762 (doc-view-goto-page page)
94dbe99c 763 (when (and
1ca678aa
MC
764 doc-view-current-search-matches
765 (y-or-n-p "No more matches before current page. Wrap to last match? "))
766 (doc-view-goto-page (caar (last doc-view-current-search-matches)))))))
94dbe99c 767
640602f7 768;;;; User interface commands and the mode
94dbe99c 769
640602f7 770(put 'doc-view-mode 'mode-class 'special)
94dbe99c 771
640602f7
RS
772;;;###autoload
773(define-derived-mode doc-view-mode nil "DocView"
774 "Major mode in DocView buffers.
775You can use \\<doc-view-mode-map>\\[doc-view-toggle-display] to
776toggle between display as a set of images and display as text."
777 :group 'doc-view
778 (make-local-variable 'doc-view-current-files)
779 (make-local-variable 'doc-view-current-doc)
780 (make-local-variable 'doc-view-current-image)
781 (make-local-variable 'doc-view-current-page)
782 (make-local-variable 'doc-view-current-converter-process)
783 (make-local-variable 'doc-view-current-timer)
784 (make-local-variable 'doc-view-current-slice)
785 (make-local-variable 'doc-view-current-cache-dir)
786 (make-local-variable 'doc-view-current-info)
787 (make-local-variable 'doc-view-current-search-matches)
788 (setq doc-view-current-doc (buffer-file-name))
789 (insert-file-contents doc-view-current-doc)
790 (use-local-map doc-view-mode-text-map)
791 (setq mode-name "DocView[text]"
792 doc-view-current-display 'text
793 buffer-read-only t
794 revert-buffer-function 'doc-view-reconvert-doc)
795 ;; Switch to image display if possible
796 (if (and (display-images-p)
797 (image-type-available-p 'png)
798 (not (get-text-property (point-min) 'display)))
799 (doc-view-toggle-display))
800 (message
801 "%s"
802 (substitute-command-keys
803 "Type \\[doc-view-toggle-display] to toggle between image and text display.")))
94dbe99c
TTN
804
805(defun doc-view-clear-cache ()
806 "Delete the whole cache (`doc-view-cache-directory')."
807 (interactive)
808 (dired-delete-file doc-view-cache-directory 'always)
809 (make-directory doc-view-cache-directory))
810
811(defun doc-view-dired-cache ()
812 "Open `dired' in `doc-view-cache-directory'."
813 (interactive)
814 (dired doc-view-cache-directory))
815
816(provide 'doc-view)
817
818;; Local Variables:
819;; mode: outline-minor
820;; End:
821
481249ca 822;; arch-tag: 5d6e5c5e-095f-489e-b4e4-1ca90a7d79be
94dbe99c 823;;; doc-view.el ends here