(x-complement-fontset-spec): Use
[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
1f75e53d 45;; representation and the source text representation of the document.
94dbe99c
TTN
46;;
47;; Since conversion may take some time all the PNG images are cached in a
48;; subdirectory of `doc-view-cache-directory' and reused when you want to view
640602f7
RS
49;; that file again. To reconvert a document hit `g' (`doc-view-reconvert-doc')
50;; when displaying the document. To delete all cached files use
94dbe99c
TTN
51;; `doc-view-clear-cache'. To open the cache with dired, so that you can tidy
52;; it out use `doc-view-dired-cache'.
53;;
54;; When conversion in underway the first page will be displayed as soon as it
55;; is available and the available pages are refreshed every
56;; `doc-view-conversion-refresh-interval' seconds. If that variable is nil the
57;; pages won't be displayed before conversion of the document finished
58;; completely.
59;;
60;; DocView lets you select a slice of the displayed pages. This slice will be
61;; remembered and applied to all pages of the current document. This enables
62;; you to cut away the margins of a document to save some space. To select a
63;; slice you can use `doc-view-set-slice' (bound to `s s') which will query you
64;; for the coordinates of the slice's top-left corner and its width and height.
65;; A much more convenient way to do the same is offered by the command
66;; `doc-view-set-slice-using-mouse' (bound to `s m'). After invokation you
67;; only have to press mouse-1 at the top-left corner and drag it to the
68;; bottom-right corner of the desired slice. To reset the slice use
69;; `doc-view-reset-slice' (bound to `s r').
70;;
94dbe99c
TTN
71;; You can also search within the document. The command `doc-view-search'
72;; (bound to `C-s') queries for a search regexp and initializes a list of all
73;; matching pages and messages how many match-pages were found. After that you
7baca0fa
JL
74;; can jump to the next page containing a match with an additional `C-s'. With
75;; `C-r' you can do the same, but backwards. To search for a new regexp give a
76;; prefix arg to one of the search functions, e.g. by typing `C-u C-s'. The
77;; searching works by using a plain text representation of the document. If
78;; that doesn't already exist the first invokation of `doc-view-search' (or
79;; `doc-view-search-backward') starts the conversion. When that finishes and
80;; you're still viewing the document (i.e. you didn't switch to another buffer)
81;; you're queried for the regexp then.
640602f7
RS
82;;
83;; Dired users can simply hit `v' on a document file. If it's a PS, PDF or DVI
84;; it will be opened using `doc-view-mode'.
85;;
94dbe99c
TTN
86
87;;; Configuration:
88
640602f7
RS
89;; If the images are too small or too big you should set the "-rXXX" option in
90;; `doc-view-ghostscript-options' to another value. (The bigger your screen,
91;; the higher the value.)
94dbe99c
TTN
92;;
93;; This and all other options can be set with the customization interface.
94;; Simply do
95;;
96;; M-x customize-group RET doc-view RET
97;;
98;; and modify them to your needs.
99
100;;; Code:
101
f4c75497
SM
102;; Todo:
103;; - better menu.
104;; - don't use `find-file'.
f4c75497 105;; - Bind slicing to a drag event.
c17587fe
SM
106;; - zoom (the whole document and/or just the region around the cursor).
107;; - get rid of the silly arrow in the fringe.
108;; - improve anti-aliasing (pdf-utils gets it better).
f4c75497 109
94dbe99c 110(require 'dired)
414dd971 111(require 'image-mode)
7baca0fa 112(require 'jka-compr)
94dbe99c
TTN
113
114;;;; Customization Options
115
116(defgroup doc-view nil
117 "In-buffer viewer for PDF, PostScript and DVI files."
118 :link '(function-link doc-view)
119 :version "22.2"
120 :group 'applications
121 :group 'multimedia
122 :prefix "doc-view-")
123
c9a9a5e3 124(defcustom doc-view-ghostscript-program (executable-find "gs")
94dbe99c 125 "Program to convert PS and PDF files to PNG."
c9a9a5e3 126 :type 'file
94dbe99c
TTN
127 :group 'doc-view)
128
129(defcustom doc-view-ghostscript-options
6a658a30
RS
130 '("-dSAFER" ;; Avoid security problems when rendering files from untrusted
131 ;; sources.
132 "-dNOPAUSE" "-sDEVICE=png16m" "-dTextAlphaBits=4"
133 "-dBATCH" "-dGraphicsAlphaBits=4" "-dQUIET" "-r100")
4b378e75 134 "A list of options to give to ghostscript."
c9a9a5e3 135 :type '(repeat string)
94dbe99c
TTN
136 :group 'doc-view)
137
c9a9a5e3 138(defcustom doc-view-dvipdfm-program (executable-find "dvipdfm")
94dbe99c
TTN
139 "Program to convert DVI files to PDF.
140
141DVI file will be converted to PDF before the resulting PDF is
142converted to PNG."
c9a9a5e3 143 :type 'file
94dbe99c
TTN
144 :group 'doc-view)
145
c9a9a5e3 146(defcustom doc-view-ps2pdf-program (executable-find "ps2pdf")
94dbe99c
TTN
147 "Program to convert PS files to PDF.
148
149PS files will be converted to PDF before searching is possible."
c9a9a5e3 150 :type 'file
94dbe99c
TTN
151 :group 'doc-view)
152
c9a9a5e3 153(defcustom doc-view-pdftotext-program (executable-find "pdftotext")
94dbe99c
TTN
154 "Program to convert PDF files to plain text.
155
156Needed for searching."
c9a9a5e3 157 :type 'file
94dbe99c
TTN
158 :group 'doc-view)
159
c17587fe 160(defcustom doc-view-cache-directory
8aafd651 161 (expand-file-name (format "docview%d" (user-uid))
bce6be12 162 temporary-file-directory)
94dbe99c 163 "The base directory, where the PNG images will be saved."
c9a9a5e3 164 :type 'directory
94dbe99c
TTN
165 :group 'doc-view)
166
167(defcustom doc-view-conversion-buffer "*doc-view conversion output*"
168 "The buffer where messages from the converter programs go to."
c9a9a5e3 169 :type 'string
94dbe99c
TTN
170 :group 'doc-view)
171
172(defcustom doc-view-conversion-refresh-interval 3
1f75e53d
GM
173 "Interval in seconds between refreshes of the DocView buffer while converting.
174After such a refresh newly converted pages will be available for
94dbe99c
TTN
175viewing. If set to nil there won't be any refreshes and the
176pages won't be displayed before conversion of the whole document
177has finished."
c9a9a5e3 178 :type 'integer
94dbe99c
TTN
179 :group 'doc-view)
180
181;;;; Internal Variables
182
183(defvar doc-view-current-files nil
184 "Only used internally.")
185
186(defvar doc-view-current-page nil
187 "Only used internally.")
188
94dbe99c
TTN
189(defvar doc-view-current-converter-process nil
190 "Only used internally.")
191
192(defvar doc-view-current-timer nil
193 "Only used internally.")
194
195(defvar doc-view-current-slice nil
196 "Only used internally.")
197
198(defvar doc-view-current-cache-dir nil
199 "Only used internally.")
200
201(defvar doc-view-current-search-matches nil
202 "Only used internally.")
203
204(defvar doc-view-current-image nil
205 "Only used internally.")
c17587fe
SM
206(defvar doc-view-current-overlay)
207(defvar doc-view-pending-cache-flush nil)
94dbe99c
TTN
208
209(defvar doc-view-current-info nil
210 "Only used internally.")
211
937cb3fb 212(defvar doc-view-previous-major-mode nil
640602f7
RS
213 "Only used internally.")
214
215;;;; DocView Keymaps
94dbe99c
TTN
216
217(defvar doc-view-mode-map
218 (let ((map (make-sparse-keymap)))
937cb3fb 219 (suppress-keymap map)
94dbe99c
TTN
220 ;; Navigation in the document
221 (define-key map (kbd "n") 'doc-view-next-page)
222 (define-key map (kbd "p") 'doc-view-previous-page)
eb8d0216
SM
223 (define-key map (kbd "<next>") 'forward-page)
224 (define-key map (kbd "<prior>") 'backward-page)
225 (define-key map [remap forward-page] 'doc-view-next-page)
226 (define-key map [remap backward-page] 'doc-view-previous-page)
94dbe99c
TTN
227 (define-key map (kbd "SPC") 'doc-view-scroll-up-or-next-page)
228 (define-key map (kbd "DEL") 'doc-view-scroll-down-or-previous-page)
229 (define-key map (kbd "M-<") 'doc-view-first-page)
230 (define-key map (kbd "M->") 'doc-view-last-page)
7656fe61 231 (define-key map [remap goto-line] 'doc-view-goto-page)
94dbe99c
TTN
232 ;; Killing/burying the buffer (and the process)
233 (define-key map (kbd "q") 'bury-buffer)
234 (define-key map (kbd "k") 'doc-view-kill-proc-and-buffer)
937cb3fb 235 (define-key map (kbd "K") 'doc-view-kill-proc)
94dbe99c
TTN
236 ;; Slicing the image
237 (define-key map (kbd "s s") 'doc-view-set-slice)
238 (define-key map (kbd "s m") 'doc-view-set-slice-using-mouse)
239 (define-key map (kbd "s r") 'doc-view-reset-slice)
240 ;; Searching
241 (define-key map (kbd "C-s") 'doc-view-search)
242 (define-key map (kbd "<find>") 'doc-view-search)
7baca0fa 243 (define-key map (kbd "C-r") 'doc-view-search-backward)
94dbe99c 244 ;; Scrolling
eb8d0216
SM
245 (define-key map [remap forward-char] 'image-forward-hscroll)
246 (define-key map [remap backward-char] 'image-backward-hscroll)
247 (define-key map [remap next-line] 'image-next-line)
248 (define-key map [remap previous-line] 'image-previous-line)
94dbe99c
TTN
249 ;; Show the tooltip
250 (define-key map (kbd "C-t") 'doc-view-show-tooltip)
640602f7
RS
251 ;; Toggle between text and image display or editing
252 (define-key map (kbd "C-c C-c") 'doc-view-toggle-display)
640602f7 253 ;; Reconvert the current document
7656fe61
SM
254 (define-key map (kbd "g") 'revert-buffer)
255 (define-key map (kbd "r") 'revert-buffer)
94dbe99c 256 map)
640602f7
RS
257 "Keymap used by `doc-view-mode' when displaying a doc as a set of images.")
258
f4c75497
SM
259(easy-menu-define doc-view-menu doc-view-mode-map
260 "Menu for Doc View mode."
261 '("DocView"
262 ["Set Slice" doc-view-set-slice-using-mouse]
263 ["Set Slice (manual)" doc-view-set-slice]
264 ["Reset Slice" doc-view-reset-slice]
265 "---"
266 ["Search" doc-view-search]
7baca0fa 267 ["Search Backwards" doc-view-search-backward]
f4c75497 268 ["Toggle display" doc-view-toggle-display]
f4c75497
SM
269 ))
270
937cb3fb 271(defvar doc-view-minor-mode-map
640602f7
RS
272 (let ((map (make-sparse-keymap)))
273 ;; Toggle between text and image display or editing
274 (define-key map (kbd "C-c C-c") 'doc-view-toggle-display)
640602f7 275 map)
937cb3fb 276 "Keymap used by `doc-minor-view-mode'.")
94dbe99c
TTN
277
278;;;; Navigation Commands
279
280(defun doc-view-goto-page (page)
281 "View the page given by PAGE."
282 (interactive "nPage: ")
283 (let ((len (length doc-view-current-files)))
284 (if (< page 1)
1ca678aa 285 (setq page 1)
94dbe99c 286 (when (> page len)
1ca678aa 287 (setq page len)))
94dbe99c 288 (setq doc-view-current-page page
1ca678aa
MC
289 doc-view-current-info
290 (concat
291 (propertize
292 (format "Page %d of %d."
293 doc-view-current-page
294 len) 'face 'bold)
295 ;; Tell user if converting isn't finished yet
296 (if doc-view-current-converter-process
297 " (still converting...)\n"
298 "\n")
299 ;; Display context infos if this page matches the last search
300 (when (and doc-view-current-search-matches
301 (assq doc-view-current-page
302 doc-view-current-search-matches))
303 (concat (propertize "Search matches:\n" 'face 'bold)
304 (let ((contexts ""))
305 (dolist (m (cdr (assq doc-view-current-page
306 doc-view-current-search-matches)))
307 (setq contexts (concat contexts " - \"" m "\"\n")))
308 contexts)))))
94dbe99c 309 ;; Update the buffer
c17587fe
SM
310 (doc-view-insert-image (nth (1- page) doc-view-current-files)
311 :pointer 'arrow)
312 (overlay-put doc-view-current-overlay 'help-echo doc-view-current-info)
313 (goto-char (point-min))
314 ;; This seems to be needed for set-window-hscroll (in
315 ;; image-forward-hscroll) to do something useful, I don't have time to
316 ;; debug this now. :-( --Stef
317 (forward-char)))
94dbe99c
TTN
318
319(defun doc-view-next-page (&optional arg)
320 "Browse ARG pages forward."
321 (interactive "p")
322 (doc-view-goto-page (+ doc-view-current-page (or arg 1))))
323
324(defun doc-view-previous-page (&optional arg)
325 "Browse ARG pages backward."
326 (interactive "p")
327 (doc-view-goto-page (- doc-view-current-page (or arg 1))))
328
329(defun doc-view-first-page ()
330 "View the first page."
331 (interactive)
332 (doc-view-goto-page 1))
333
334(defun doc-view-last-page ()
335 "View the last page."
336 (interactive)
337 (doc-view-goto-page (length doc-view-current-files)))
338
339(defun doc-view-scroll-up-or-next-page ()
340 "Scroll page up if possible, else goto next page."
341 (interactive)
342 (condition-case nil
343 (scroll-up)
344 (error (doc-view-next-page))))
345
346(defun doc-view-scroll-down-or-previous-page ()
347 "Scroll page down if possible, else goto previous page."
348 (interactive)
349 (condition-case nil
350 (scroll-down)
351 (error (doc-view-previous-page)
1ca678aa 352 (goto-char (point-max)))))
94dbe99c 353
937cb3fb
GM
354;;;; Utility Functions
355
640602f7
RS
356(defun doc-view-kill-proc ()
357 "Kill the current converter process."
358 (interactive)
359 (when doc-view-current-converter-process
937cb3fb
GM
360 (kill-process doc-view-current-converter-process)
361 (setq doc-view-current-converter-process nil))
640602f7
RS
362 (when doc-view-current-timer
363 (cancel-timer doc-view-current-timer)
364 (setq doc-view-current-timer nil))
365 (setq mode-line-process nil))
366
94dbe99c
TTN
367(defun doc-view-kill-proc-and-buffer ()
368 "Kill the current converter process and buffer."
369 (interactive)
640602f7 370 (doc-view-kill-proc)
94dbe99c 371 (when (eq major-mode 'doc-view-mode)
94dbe99c
TTN
372 (kill-buffer (current-buffer))))
373
c17587fe
SM
374(defun doc-view-make-safe-dir (dir)
375 (condition-case nil
376 (let ((umask (default-file-modes)))
377 (unwind-protect
378 (progn
379 ;; Create temp files with strict access rights. It's easy to
380 ;; loosen them later, whereas it's impossible to close the
381 ;; time-window of loose permissions otherwise.
382 (set-default-file-modes #o0700)
383 (make-directory dir))
384 ;; Reset the umask.
385 (set-default-file-modes umask)))
386 (file-already-exists
387 (if (file-symlink-p dir)
388 (error "Danger: %s points to a symbolic link" dir))
389 ;; In case it was created earlier with looser rights.
390 ;; We could check the mode info returned by file-attributes, but it's
391 ;; a pain to parse and it may not tell you what we want under
392 ;; non-standard file-systems. So let's just say what we want and let
393 ;; the underlying C code and file-system figure it out.
394 ;; This also ends up checking a bunch of useful conditions: it makes
395 ;; sure we have write-access to the directory and that we own it, thus
396 ;; closing a bunch of security holes.
397 (set-file-modes dir #o0700))))
398
937cb3fb
GM
399(defun doc-view-current-cache-dir ()
400 "Return the directory where the png files of the current doc should be saved.
401It's a subdirectory of `doc-view-cache-directory'."
402 (if doc-view-current-cache-dir
403 doc-view-current-cache-dir
c17587fe
SM
404 ;; Try and make sure doc-view-cache-directory exists and is safe.
405 (doc-view-make-safe-dir doc-view-cache-directory)
406 ;; Now compute the subdirectory to use.
937cb3fb
GM
407 (setq doc-view-current-cache-dir
408 (file-name-as-directory
c17587fe
SM
409 (expand-file-name
410 (let ((doc buffer-file-name))
411 (concat (file-name-nondirectory doc)
412 "-"
413 (with-temp-buffer
414 (insert-file-contents-literally doc)
415 (md5 (current-buffer)))))
416 doc-view-cache-directory)))))
937cb3fb
GM
417
418(defun doc-view-remove-if (predicate list)
419 "Return LIST with all items removed that satisfy PREDICATE."
420 (let (new-list)
421 (dolist (item list (nreverse new-list))
422 (when (not (funcall predicate item))
423 (setq new-list (cons item new-list))))))
424
94dbe99c
TTN
425;;;; Conversion Functions
426
c17587fe 427(defun doc-view-reconvert-doc ()
640602f7
RS
428 "Reconvert the current document.
429Should be invoked when the cached images aren't up-to-date."
430 (interactive)
f4c75497
SM
431 (doc-view-kill-proc)
432 ;; Clear the old cached files
433 (when (file-exists-p (doc-view-current-cache-dir))
434 (dired-delete-file (doc-view-current-cache-dir) 'always))
c17587fe 435 (doc-view-initiate-display))
94dbe99c
TTN
436
437(defun doc-view-dvi->pdf-sentinel (proc event)
e48a5bf9 438 "If DVI->PDF conversion was successful, convert the PDF to PNG now."
94dbe99c
TTN
439 (if (not (string-match "finished" event))
440 (message "DocView: dvi->pdf process changed status to %s." event)
441 (set-buffer (process-get proc 'buffer))
640602f7
RS
442 (setq doc-view-current-converter-process nil
443 mode-line-process nil)
94dbe99c
TTN
444 ;; Now go on converting this PDF to a set of PNG files.
445 (let* ((pdf (process-get proc 'pdf-file))
c17587fe
SM
446 (png (expand-file-name "page-%d.png"
447 (doc-view-current-cache-dir))))
94dbe99c
TTN
448 (doc-view-pdf/ps->png pdf png))))
449
450(defun doc-view-dvi->pdf (dvi pdf)
1f75e53d 451 "Convert DVI to PDF asynchronously."
94dbe99c 452 (setq doc-view-current-converter-process
640602f7 453 (start-process "dvi->pdf" doc-view-conversion-buffer
1ca678aa 454 doc-view-dvipdfm-program
640602f7
RS
455 "-o" pdf dvi)
456 mode-line-process (list (format ":%s" doc-view-current-converter-process)))
94dbe99c 457 (set-process-sentinel doc-view-current-converter-process
1ca678aa 458 'doc-view-dvi->pdf-sentinel)
94dbe99c
TTN
459 (process-put doc-view-current-converter-process 'buffer (current-buffer))
460 (process-put doc-view-current-converter-process 'pdf-file pdf))
461
462(defun doc-view-pdf/ps->png-sentinel (proc event)
463 "If PDF/PS->PNG conversion was successful, update the display."
464 (if (not (string-match "finished" event))
465 (message "DocView: converter process changed status to %s." event)
466 (set-buffer (process-get proc 'buffer))
640602f7
RS
467 (setq doc-view-current-converter-process nil
468 mode-line-process nil)
94dbe99c
TTN
469 (when doc-view-current-timer
470 (cancel-timer doc-view-current-timer)
471 (setq doc-view-current-timer nil))
94dbe99c 472 ;; Yippie, finished. Update the display!
f4c75497 473 (doc-view-display buffer-file-name)))
94dbe99c
TTN
474
475(defun doc-view-pdf/ps->png (pdf-ps png)
1f75e53d 476 "Convert PDF-PS to PNG asynchronously."
94dbe99c 477 (setq doc-view-current-converter-process
1ca678aa 478 (apply 'start-process
640602f7 479 (append (list "pdf/ps->png" doc-view-conversion-buffer
1ca678aa
MC
480 doc-view-ghostscript-program)
481 doc-view-ghostscript-options
482 (list (concat "-sOutputFile=" png))
640602f7
RS
483 (list pdf-ps)))
484 mode-line-process (list (format ":%s" doc-view-current-converter-process)))
94dbe99c 485 (process-put doc-view-current-converter-process
1ca678aa 486 'buffer (current-buffer))
94dbe99c 487 (set-process-sentinel doc-view-current-converter-process
1ca678aa 488 'doc-view-pdf/ps->png-sentinel)
94dbe99c
TTN
489 (when doc-view-conversion-refresh-interval
490 (setq doc-view-current-timer
1ca678aa 491 (run-at-time "1 secs" doc-view-conversion-refresh-interval
937cb3fb 492 'doc-view-display
f4c75497 493 buffer-file-name))))
94dbe99c
TTN
494
495(defun doc-view-pdf->txt-sentinel (proc event)
496 (if (not (string-match "finished" event))
497 (message "DocView: converter process changed status to %s." event)
498 (let ((current-buffer (current-buffer))
1ca678aa 499 (proc-buffer (process-get proc 'buffer)))
94dbe99c 500 (set-buffer proc-buffer)
640602f7
RS
501 (setq doc-view-current-converter-process nil
502 mode-line-process nil)
94dbe99c
TTN
503 ;; If the user looks at the DocView buffer where the conversion was
504 ;; performed, search anew. This time it will be queried for a regexp.
505 (when (eq current-buffer proc-buffer)
7baca0fa 506 (doc-view-search nil)))))
94dbe99c
TTN
507
508(defun doc-view-pdf->txt (pdf txt)
1f75e53d 509 "Convert PDF to TXT asynchronously."
94dbe99c 510 (setq doc-view-current-converter-process
640602f7 511 (start-process "pdf->txt" doc-view-conversion-buffer
1ca678aa 512 doc-view-pdftotext-program "-raw"
640602f7
RS
513 pdf txt)
514 mode-line-process (list (format ":%s" doc-view-current-converter-process)))
94dbe99c 515 (set-process-sentinel doc-view-current-converter-process
1ca678aa 516 'doc-view-pdf->txt-sentinel)
94dbe99c
TTN
517 (process-put doc-view-current-converter-process 'buffer (current-buffer)))
518
519(defun doc-view-ps->pdf-sentinel (proc event)
520 (if (not (string-match "finished" event))
521 (message "DocView: converter process changed status to %s." event)
522 (set-buffer (process-get proc 'buffer))
640602f7
RS
523 (setq doc-view-current-converter-process nil
524 mode-line-process nil)
94dbe99c
TTN
525 ;; Now we can transform to plain text.
526 (doc-view-pdf->txt (process-get proc 'pdf-file)
c17587fe
SM
527 (expand-file-name "doc.txt"
528 (doc-view-current-cache-dir)))))
94dbe99c
TTN
529
530(defun doc-view-ps->pdf (ps pdf)
531 "Convert PS to PDF asynchronously."
94dbe99c 532 (setq doc-view-current-converter-process
640602f7 533 (start-process "ps->pdf" doc-view-conversion-buffer
1ca678aa 534 doc-view-ps2pdf-program
6a658a30
RS
535 ;; Avoid security problems when rendering files from
536 ;; untrusted sources.
937cb3fb
GM
537 "-dSAFER"
538 ;; in-file and out-file
539 ps pdf)
640602f7 540 mode-line-process (list (format ":%s" doc-view-current-converter-process)))
94dbe99c 541 (set-process-sentinel doc-view-current-converter-process
1ca678aa 542 'doc-view-ps->pdf-sentinel)
94dbe99c
TTN
543 (process-put doc-view-current-converter-process 'buffer (current-buffer))
544 (process-put doc-view-current-converter-process 'pdf-file pdf))
545
640602f7 546(defun doc-view-convert-current-doc ()
f4c75497 547 "Convert `buffer-file-name' to a set of png files, one file per page.
640602f7
RS
548Those files are saved in the directory given by the function
549`doc-view-current-cache-dir'."
c17587fe
SM
550 ;; Let stale files still display while we recompute the new ones, so only
551 ;; flush the cache when the conversion is over. One of the reasons why it
552 ;; is important to keep displaying the stale page is so that revert-buffer
553 ;; preserves the horizontal/vertical scroll settings (which are otherwise
554 ;; resets during the redisplay).
555 (setq doc-view-pending-cache-flush t)
556 (let ((png-file (expand-file-name "page-%d.png"
557 (doc-view-current-cache-dir))))
558 (make-directory (doc-view-current-cache-dir))
f4c75497 559 (if (not (string= (file-name-extension buffer-file-name) "dvi"))
1ca678aa 560 ;; Convert to PNG images.
f4c75497 561 (doc-view-pdf/ps->png buffer-file-name png-file)
4b378e75 562 ;; DVI files have to be converted to PDF before Ghostscript can process
94dbe99c 563 ;; it.
f4c75497 564 (doc-view-dvi->pdf buffer-file-name
c17587fe
SM
565 (expand-file-name "doc.pdf"
566 doc-view-current-cache-dir)))))
94dbe99c 567
94dbe99c
TTN
568;;;; Slicing
569
570(defun doc-view-set-slice (x y width height)
571 "Set the slice of the images that should be displayed.
572You can use this function to tell doc-view not to display the
573margins of the document. It prompts for the top-left corner (X
574and Y) of the slice to display and its WIDTH and HEIGHT.
575
576See `doc-view-set-slice-using-mouse' for a more convenient way to
577do that. To reset the slice use `doc-view-reset-slice'."
578 (interactive
579 (let* ((size (image-size doc-view-current-image t))
1ca678aa
MC
580 (a (read-number (format "Top-left X (0..%d): " (car size))))
581 (b (read-number (format "Top-left Y (0..%d): " (cdr size))))
582 (c (read-number (format "Width (0..%d): " (- (car size) a))))
583 (d (read-number (format "Height (0..%d): " (- (cdr size) b)))))
94dbe99c
TTN
584 (list a b c d)))
585 (setq doc-view-current-slice (list x y width height))
586 ;; Redisplay
587 (doc-view-goto-page doc-view-current-page))
588
589(defun doc-view-set-slice-using-mouse ()
590 "Set the slice of the images that should be displayed.
591You set the slice by pressing mouse-1 at its top-left corner and
592dragging it to its bottom-right corner. See also
593`doc-view-set-slice' and `doc-view-reset-slice'."
594 (interactive)
595 (let (x y w h done)
596 (while (not done)
597 (let ((e (read-event
1ca678aa
MC
598 (concat "Press mouse-1 at the top-left corner and "
599 "drag it to the bottom-right corner!"))))
600 (when (eq (car e) 'drag-mouse-1)
601 (setq x (car (posn-object-x-y (event-start e))))
602 (setq y (cdr (posn-object-x-y (event-start e))))
603 (setq w (- (car (posn-object-x-y (event-end e))) x))
604 (setq h (- (cdr (posn-object-x-y (event-end e))) y))
605 (setq done t))))
94dbe99c
TTN
606 (doc-view-set-slice x y w h)))
607
608(defun doc-view-reset-slice ()
e48a5bf9 609 "Reset the current slice.
1f75e53d 610After calling this function whole pages will be visible again."
94dbe99c
TTN
611 (interactive)
612 (setq doc-view-current-slice nil)
613 ;; Redisplay
614 (doc-view-goto-page doc-view-current-page))
615
616;;;; Display
617
618(defun doc-view-insert-image (file &rest args)
619 "Insert the given png FILE.
e48a5bf9 620ARGS is a list of image descriptors."
c17587fe
SM
621 (when doc-view-pending-cache-flush
622 (clear-image-cache)
623 (setq doc-view-pending-cache-flush nil))
94dbe99c
TTN
624 (let ((image (apply 'create-image file 'png nil args)))
625 (setq doc-view-current-image image)
c17587fe
SM
626 (move-overlay doc-view-current-overlay (point-min) (point-max))
627 (overlay-put doc-view-current-overlay 'display
628 (if doc-view-current-slice
629 (list (cons 'slice doc-view-current-slice) image)
630 image))))
94dbe99c
TTN
631
632(defun doc-view-sort (a b)
633 "Return non-nil if A should be sorted before B.
634Predicate for sorting `doc-view-current-files'."
f4c75497
SM
635 (or (< (length a) (length b))
636 (and (= (length a) (length b))
637 (string< a b))))
94dbe99c
TTN
638
639(defun doc-view-display (doc)
640 "Start viewing the document DOC."
640602f7
RS
641 (set-buffer (get-file-buffer doc))
642 (setq doc-view-current-files
643 (sort (directory-files (doc-view-current-cache-dir) t
644 "page-[0-9]+\\.png" t)
645 'doc-view-sort))
646 (when (> (length doc-view-current-files) 0)
647 (doc-view-goto-page doc-view-current-page)))
94dbe99c
TTN
648
649(defun doc-view-buffer-message ()
c17587fe
SM
650 ;; Only show this message initially, not when refreshing the buffer (in which
651 ;; case it's better to keep displaying the "stale" page while computing
652 ;; the fresh new ones).
653 (unless (overlay-get doc-view-current-overlay 'display)
654 (overlay-put doc-view-current-overlay 'display
655 (concat (propertize "Welcome to DocView!" 'face 'bold)
656 "\n"
657 "
1f75e53d 658If you see this buffer it means that the document you want to view is being
f4c75497 659converted to PNG and the conversion of the first page hasn't finished yet or
94dbe99c
TTN
660`doc-view-conversion-refresh-interval' is set to nil.
661
662For now these keys are useful:
663
1ca678aa 664`q' : Bury this buffer. Conversion will go on in background.
937cb3fb 665`k' : Kill the conversion process and this buffer.
c17587fe 666`K' : Kill the conversion process.\n"))))
94dbe99c
TTN
667
668(defun doc-view-show-tooltip ()
669 (interactive)
670 (tooltip-show doc-view-current-info))
671
937cb3fb 672;;;;; Toggle between editing and viewing
640602f7
RS
673
674(defun doc-view-toggle-display ()
937cb3fb 675 "Toggle between editing a document as text or viewing it."
640602f7 676 (interactive)
937cb3fb
GM
677 (if (eq major-mode 'doc-view-mode)
678 ;; Switch to editing mode
679 (progn
680 (doc-view-kill-proc)
681 (setq buffer-read-only nil)
c17587fe 682 (delete-overlay doc-view-current-overlay)
937cb3fb
GM
683 ;; Switch to the previously used major mode or fall back to fundamental
684 ;; mode.
685 (if doc-view-previous-major-mode
686 (funcall doc-view-previous-major-mode)
687 (fundamental-mode))
c17587fe 688 (doc-view-minor-mode 1))
937cb3fb
GM
689 ;; Switch to doc-view-mode
690 (when (and (buffer-modified-p)
691 (y-or-n-p "The buffer has been modified. Save the changes? "))
692 (save-buffer))
937cb3fb 693 (doc-view-mode)))
640602f7 694
94dbe99c
TTN
695;;;; Searching
696
697(defun doc-view-search-internal (regexp file)
698 "Return a list of FILE's pages that contain text matching REGEXP.
1ca678aa
MC
699The value is an alist of the form (PAGE CONTEXTS) where PAGE is
700the pagenumber and CONTEXTS are all lines of text containing a match."
94dbe99c
TTN
701 (with-temp-buffer
702 (insert-file-contents file)
703 (let ((page 1)
1ca678aa
MC
704 (lastpage 1)
705 matches)
94dbe99c 706 (while (re-search-forward (concat "\\(?:\\([\f]\\)\\|\\("
1ca678aa
MC
707 regexp "\\)\\)") nil t)
708 (when (match-string 1) (incf page))
709 (when (match-string 2)
710 (if (/= page lastpage)
c17587fe
SM
711 (push (cons page
712 (list (buffer-substring
713 (line-beginning-position)
714 (line-end-position))))
715 matches)
1ca678aa
MC
716 (setq matches (cons
717 (append
718 (or
719 ;; This page already is a match.
720 (car matches)
721 ;; This is the first match on page.
722 (list page))
723 (list (buffer-substring
724 (line-beginning-position)
725 (line-end-position))))
726 (cdr matches))))
727 (setq lastpage page)))
94dbe99c
TTN
728 (nreverse matches))))
729
730(defun doc-view-search-no-of-matches (list)
731 "Extract the number of matches from the search result LIST."
732 (let ((no 0))
733 (dolist (p list)
734 (setq no (+ no (1- (length p)))))
735 no))
736
7baca0fa
JL
737(defun doc-view-search-backward (new-query)
738 "Call `doc-view-search' for backward search.
739If prefix NEW-QUERY is given, ask for a new regexp."
740 (interactive "P")
741 (doc-view-search arg t))
742
743(defun doc-view-search (new-query &optional backward)
744 "Jump to the next match or initiate a new search if NEW-QUERY is given.
94dbe99c 745If the current document hasn't been transformed to plain text
7baca0fa
JL
746till now do that first.
747If BACKWARD is non-nil, jump to the previous match."
748 (interactive "P")
749 (if (and (not arg)
750 doc-view-current-search-matches)
751 (if backward
752 (doc-view-search-previous-match 1)
753 (doc-view-search-next-match 1))
754 ;; New search, so forget the old results.
755 (setq doc-view-current-search-matches nil)
756 (let ((txt (expand-file-name "doc.txt"
757 (doc-view-current-cache-dir))))
758 (if (file-readable-p txt)
759 (progn
760 (setq doc-view-current-search-matches
761 (doc-view-search-internal
762 (read-from-minibuffer "Regexp: ")
763 txt))
764 (message "DocView: search yielded %d matches."
765 (doc-view-search-no-of-matches
766 doc-view-current-search-matches)))
767 ;; We must convert to TXT first!
768 (if doc-view-current-converter-process
769 (message "DocView: please wait till conversion finished.")
770 (let ((ext (file-name-extension buffer-file-name)))
771 (cond
772 ((string= ext "pdf")
773 ;; Doc is a PDF, so convert it to TXT
774 (doc-view-pdf->txt buffer-file-name txt))
775 ((string= ext "ps")
776 ;; Doc is a PS, so convert it to PDF (which will be converted to
777 ;; TXT thereafter).
778 (doc-view-ps->pdf buffer-file-name
779 (expand-file-name "doc.pdf"
780 (doc-view-current-cache-dir))))
781 ((string= ext "dvi")
782 ;; Doc is a DVI. This means that a doc.pdf already exists in its
783 ;; cache subdirectory.
784 (doc-view-pdf->txt (expand-file-name "doc.pdf"
785 (doc-view-current-cache-dir))
786 txt))
787 (t (error "DocView doesn't know what to do")))))))))
94dbe99c
TTN
788
789(defun doc-view-search-next-match (arg)
790 "Go to the ARGth next matching page."
791 (interactive "p")
937cb3fb
GM
792 (let* ((next-pages (doc-view-remove-if
793 (lambda (i) (<= (car i) doc-view-current-page))
794 doc-view-current-search-matches))
1ca678aa 795 (page (car (nth (1- arg) next-pages))))
94dbe99c 796 (if page
1ca678aa 797 (doc-view-goto-page page)
94dbe99c 798 (when (and
1ca678aa
MC
799 doc-view-current-search-matches
800 (y-or-n-p "No more matches after current page. Wrap to first match? "))
801 (doc-view-goto-page (caar doc-view-current-search-matches))))))
94dbe99c
TTN
802
803(defun doc-view-search-previous-match (arg)
804 "Go to the ARGth previous matching page."
805 (interactive "p")
937cb3fb
GM
806 (let* ((prev-pages (doc-view-remove-if
807 (lambda (i) (>= (car i) doc-view-current-page))
808 doc-view-current-search-matches))
1ca678aa 809 (page (car (nth (1- arg) (nreverse prev-pages)))))
94dbe99c 810 (if page
1ca678aa 811 (doc-view-goto-page page)
94dbe99c 812 (when (and
1ca678aa
MC
813 doc-view-current-search-matches
814 (y-or-n-p "No more matches before current page. Wrap to last match? "))
815 (doc-view-goto-page (caar (last doc-view-current-search-matches)))))))
94dbe99c 816
640602f7 817;;;; User interface commands and the mode
94dbe99c 818
c17587fe
SM
819;; (put 'doc-view-mode 'mode-class 'special)
820
821(defun doc-view-initiate-display ()
822 ;; Switch to image display if possible
823 (if (and (display-images-p)
824 (image-type-available-p 'png))
825 (progn
826 (doc-view-buffer-message)
827 (setq doc-view-current-page (or doc-view-current-page 1))
828 (if (file-exists-p (doc-view-current-cache-dir))
829 (progn
830 (message "DocView: using cached files!")
831 (doc-view-display buffer-file-name))
832 (doc-view-convert-current-doc))
833 (message
834 "%s"
835 (substitute-command-keys
836 (concat "Type \\[doc-view-toggle-display] to toggle between "
837 "editing or viewing the document."))))
838 (message
839 "%s"
840 (substitute-command-keys
841 (concat "No image (png) support available. Type \\[doc-view-toggle-display] "
842 "to switch to an editing mode.")))))
94dbe99c 843
640602f7 844;;;###autoload
937cb3fb 845(defun doc-view-mode ()
640602f7
RS
846 "Major mode in DocView buffers.
847You can use \\<doc-view-mode-map>\\[doc-view-toggle-display] to
937cb3fb
GM
848toggle between displaying the document or editing it as text."
849 (interactive)
7baca0fa
JL
850 (if jka-compr-really-do-compress
851 ;; This is a compressed file uncompressed by auto-compression-mode.
852 (when (y-or-n-p (concat "DocView: Cannot convert compressed file. "
853 "Save it uncompressed first? "))
854 (let ((file (read-file-name
855 "File: "
856 (file-name-directory buffer-file-name))))
857 (write-region (point-min) (point-max) file)
858 (kill-buffer nil)
859 (find-file file)
860 (doc-view-mode)))
861 (let* ((prev-major-mode (if (eq major-mode 'doc-view-mode)
862 doc-view-previous-major-mode
863 major-mode)))
864 (kill-all-local-variables)
865 (set (make-local-variable 'doc-view-previous-major-mode) prev-major-mode))
866 (make-local-variable 'doc-view-current-files)
867 (make-local-variable 'doc-view-current-image)
868 (make-local-variable 'doc-view-current-page)
869 (make-local-variable 'doc-view-current-converter-process)
870 (make-local-variable 'doc-view-current-timer)
871 (make-local-variable 'doc-view-current-slice)
872 (make-local-variable 'doc-view-current-cache-dir)
873 (make-local-variable 'doc-view-current-info)
874 (make-local-variable 'doc-view-current-search-matches)
875 (set (make-local-variable 'doc-view-current-overlay)
876 (make-overlay (point-min) (point-max) nil t))
877 (add-hook 'change-major-mode-hook
878 (lambda () (delete-overlay doc-view-current-overlay))
879 nil t)
880 (set (make-local-variable 'mode-line-position)
881 '(" P" (:eval (number-to-string doc-view-current-page))
882 "/" (:eval (number-to-string (length doc-view-current-files)))))
883 (set (make-local-variable 'cursor-type) nil)
884 (use-local-map doc-view-mode-map)
885 (set (make-local-variable 'after-revert-hook) 'doc-view-reconvert-doc)
886 (setq mode-name "DocView"
887 buffer-read-only t
888 major-mode 'doc-view-mode)
889 (doc-view-initiate-display)
890 (run-mode-hooks 'doc-view-mode-hook)))
937cb3fb
GM
891
892;;;###autoload
893(define-minor-mode doc-view-minor-mode
894 "Toggle Doc view minor mode.
895With arg, turn Doc view minor mode on if arg is positive, off otherwise.
896See the command `doc-view-mode' for more information on this mode."
897 nil " DocView" doc-view-minor-mode-map
898 :group 'doc-view
899 (when doc-view-minor-mode
900 (add-hook 'change-major-mode-hook (lambda () (doc-view-minor-mode -1)) nil t)
901 (message
902 "%s"
903 (substitute-command-keys
904 "Type \\[doc-view-toggle-display] to toggle between editing or viewing the document."))))
94dbe99c
TTN
905
906(defun doc-view-clear-cache ()
907 "Delete the whole cache (`doc-view-cache-directory')."
908 (interactive)
909 (dired-delete-file doc-view-cache-directory 'always)
910 (make-directory doc-view-cache-directory))
911
912(defun doc-view-dired-cache ()
913 "Open `dired' in `doc-view-cache-directory'."
914 (interactive)
915 (dired doc-view-cache-directory))
916
917(provide 'doc-view)
918
919;; Local Variables:
920;; mode: outline-minor
921;; End:
922
481249ca 923;; arch-tag: 5d6e5c5e-095f-489e-b4e4-1ca90a7d79be
94dbe99c 924;;; doc-view.el ends here