Switch to recommended form of GPLv3 permissions notice.
[bpt/emacs.git] / lisp / doc-view.el
CommitLineData
94dbe99c
TTN
1;;; doc-view.el --- View PDF/PostScript/DVI files in Emacs
2
069b4ce3 3;; Copyright (C) 2007, 2008 Free Software Foundation, Inc.
94dbe99c
TTN
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
eb3fa2cf 11;; GNU Emacs is free software: you can redistribute it and/or modify
94dbe99c 12;; it under the terms of the GNU General Public License as published by
eb3fa2cf
GM
13;; the Free Software Foundation, either version 3 of the License, or
14;; (at your option) any later version.
94dbe99c
TTN
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
eb3fa2cf 22;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
94dbe99c
TTN
23
24;;; Requirements:
25
4b378e75 26;; doc-view.el requires GNU Emacs 22.1 or newer. You also need Ghostscript,
1ecc9da7
TH
27;; `dvipdf' (comes with Ghostscript) or `dvipdfm' (comes with teTeX or TeXLive)
28;; and `pdftotext', which comes with xpdf (http://www.foolabs.com/xpdf/) or
29;; poppler (http://poppler.freedesktop.org/).
94dbe99c
TTN
30
31;;; Commentary:
32
33;; DocView is a document viewer for Emacs. It converts PDF, PS and DVI files
34;; to a set of PNG files, one PNG for each page, and displays the PNG images
35;; inside an Emacs buffer. This buffer uses `doc-view-mode' which provides
36;; convenient key bindings for browsing the document.
37;;
640602f7 38;; To use it simply open a document file with
94dbe99c 39;;
640602f7 40;; C-x C-f ~/path/to/document RET
94dbe99c 41;;
640602f7
RS
42;; and the document will be converted and displayed, if your emacs supports png
43;; images. With `C-c C-c' you can toggle between the rendered images
1f75e53d 44;; representation and the source text representation of the document.
94dbe99c
TTN
45;;
46;; Since conversion may take some time all the PNG images are cached in a
47;; subdirectory of `doc-view-cache-directory' and reused when you want to view
640602f7
RS
48;; that file again. To reconvert a document hit `g' (`doc-view-reconvert-doc')
49;; when displaying the document. To delete all cached files use
94dbe99c
TTN
50;; `doc-view-clear-cache'. To open the cache with dired, so that you can tidy
51;; it out use `doc-view-dired-cache'.
52;;
53;; When conversion in underway the first page will be displayed as soon as it
54;; is available and the available pages are refreshed every
55;; `doc-view-conversion-refresh-interval' seconds. If that variable is nil the
56;; pages won't be displayed before conversion of the document finished
57;; completely.
58;;
59;; DocView lets you select a slice of the displayed pages. This slice will be
60;; remembered and applied to all pages of the current document. This enables
61;; you to cut away the margins of a document to save some space. To select a
62;; slice you can use `doc-view-set-slice' (bound to `s s') which will query you
63;; for the coordinates of the slice's top-left corner and its width and height.
64;; A much more convenient way to do the same is offered by the command
65;; `doc-view-set-slice-using-mouse' (bound to `s m'). After invokation you
66;; only have to press mouse-1 at the top-left corner and drag it to the
67;; bottom-right corner of the desired slice. To reset the slice use
68;; `doc-view-reset-slice' (bound to `s r').
69;;
94dbe99c
TTN
70;; You can also search within the document. The command `doc-view-search'
71;; (bound to `C-s') queries for a search regexp and initializes a list of all
72;; matching pages and messages how many match-pages were found. After that you
7baca0fa
JL
73;; can jump to the next page containing a match with an additional `C-s'. With
74;; `C-r' you can do the same, but backwards. To search for a new regexp give a
75;; prefix arg to one of the search functions, e.g. by typing `C-u C-s'. The
76;; searching works by using a plain text representation of the document. If
77;; that doesn't already exist the first invokation of `doc-view-search' (or
78;; `doc-view-search-backward') starts the conversion. When that finishes and
79;; you're still viewing the document (i.e. you didn't switch to another buffer)
80;; you're queried for the regexp then.
640602f7
RS
81;;
82;; Dired users can simply hit `v' on a document file. If it's a PS, PDF or DVI
83;; it will be opened using `doc-view-mode'.
84;;
94dbe99c
TTN
85
86;;; Configuration:
87
640602f7
RS
88;; If the images are too small or too big you should set the "-rXXX" option in
89;; `doc-view-ghostscript-options' to another value. (The bigger your screen,
90;; the higher the value.)
94dbe99c
TTN
91;;
92;; This and all other options can be set with the customization interface.
93;; Simply do
94;;
95;; M-x customize-group RET doc-view RET
96;;
97;; and modify them to your needs.
98
34065e5e 99;;; Todo:
94dbe99c 100
56741510
SM
101;; - add print command.
102;; - share more code with image-mode.
f4c75497 103;; - better menu.
f4c75497 104;; - Bind slicing to a drag event.
eb79098b 105;; - doc-view-fit-doc-to-window and doc-view-fit-window-to-doc?
cec1df02 106;; - zoom the region around the cursor (like xdvi).
c17587fe
SM
107;; - get rid of the silly arrow in the fringe.
108;; - improve anti-aliasing (pdf-utils gets it better).
f4c75497 109
34065e5e
JL
110;;;; About isearch support
111
112;; I tried implementing isearch by setting
113;; `isearch-search-fun-function' buffer-locally, but that didn't
114;; work too good. The function doing the real search was called
115;; endlessly somehow. But even if we'd get that working no real
116;; isearch feeling comes up due to the missing match highlighting.
117;; Currently I display all lines containing a match in a tooltip and
118;; each C-s or C-r jumps directly to the next/previous page with a
119;; match. With isearch we could only display the current match. So
120;; we had to decide if another C-s jumps to the next page with a
121;; match (thus only the first match in a page will be displayed in a
122;; tooltip) or to the next match, which would do nothing visible
123;; (except the tooltip) if the next match is on the same page.
124
125;; And it's much slower than the current search facility, because
126;; isearch really searches for each step forward or backward wheras
127;; the current approach searches once and then it knows to which
128;; pages to jump.
129
130;; Anyway, if someone with better isearch knowledge wants to give it a try,
131;; feel free to do it. --Tassilo
132
133;;; Code:
134
23cda572 135(eval-when-compile (require 'cl))
94dbe99c 136(require 'dired)
414dd971 137(require 'image-mode)
7baca0fa 138(require 'jka-compr)
94dbe99c
TTN
139
140;;;; Customization Options
141
142(defgroup doc-view nil
143 "In-buffer viewer for PDF, PostScript and DVI files."
144 :link '(function-link doc-view)
145 :version "22.2"
146 :group 'applications
147 :group 'multimedia
148 :prefix "doc-view-")
149
c9a9a5e3 150(defcustom doc-view-ghostscript-program (executable-find "gs")
94dbe99c 151 "Program to convert PS and PDF files to PNG."
c9a9a5e3 152 :type 'file
94dbe99c
TTN
153 :group 'doc-view)
154
155(defcustom doc-view-ghostscript-options
6a658a30
RS
156 '("-dSAFER" ;; Avoid security problems when rendering files from untrusted
157 ;; sources.
158 "-dNOPAUSE" "-sDEVICE=png16m" "-dTextAlphaBits=4"
05477667 159 "-dBATCH" "-dGraphicsAlphaBits=4" "-dQUIET")
4b378e75 160 "A list of options to give to ghostscript."
c9a9a5e3 161 :type '(repeat string)
94dbe99c
TTN
162 :group 'doc-view)
163
05477667
SM
164(defcustom doc-view-resolution 100
165 "Dots per inch resolution used to render the documents.
166Higher values result in larger images."
9efa445f
DN
167 :type 'number
168 :group 'doc-view)
05477667 169
c9a9a5e3 170(defcustom doc-view-dvipdfm-program (executable-find "dvipdfm")
94dbe99c
TTN
171 "Program to convert DVI files to PDF.
172
173DVI file will be converted to PDF before the resulting PDF is
53d4c024
TH
174converted to PNG.
175
176If this and `doc-view-dvipdf-program' are set,
177`doc-view-dvipdf-program' will be preferred."
178 :type 'file
179 :group 'doc-view)
180
181(defcustom doc-view-dvipdf-program (executable-find "dvipdf")
182 "Program to convert DVI files to PDF.
183
184DVI file will be converted to PDF before the resulting PDF is
185converted to PNG.
186
187If this and `doc-view-dvipdfm-program' are set,
188`doc-view-dvipdf-program' will be preferred."
c9a9a5e3 189 :type 'file
94dbe99c
TTN
190 :group 'doc-view)
191
c9a9a5e3 192(defcustom doc-view-ps2pdf-program (executable-find "ps2pdf")
94dbe99c
TTN
193 "Program to convert PS files to PDF.
194
195PS files will be converted to PDF before searching is possible."
c9a9a5e3 196 :type 'file
94dbe99c
TTN
197 :group 'doc-view)
198
c9a9a5e3 199(defcustom doc-view-pdftotext-program (executable-find "pdftotext")
94dbe99c
TTN
200 "Program to convert PDF files to plain text.
201
202Needed for searching."
c9a9a5e3 203 :type 'file
94dbe99c
TTN
204 :group 'doc-view)
205
c17587fe 206(defcustom doc-view-cache-directory
8aafd651 207 (expand-file-name (format "docview%d" (user-uid))
bce6be12 208 temporary-file-directory)
94dbe99c 209 "The base directory, where the PNG images will be saved."
c9a9a5e3 210 :type 'directory
94dbe99c
TTN
211 :group 'doc-view)
212
56741510
SM
213(defvar doc-view-conversion-buffer " *doc-view conversion output*"
214 "The buffer where messages from the converter programs go to.")
94dbe99c 215
56741510 216(defcustom doc-view-conversion-refresh-interval 1
1f75e53d
GM
217 "Interval in seconds between refreshes of the DocView buffer while converting.
218After such a refresh newly converted pages will be available for
94dbe99c
TTN
219viewing. If set to nil there won't be any refreshes and the
220pages won't be displayed before conversion of the whole document
221has finished."
c9a9a5e3 222 :type 'integer
94dbe99c
TTN
223 :group 'doc-view)
224
225;;;; Internal Variables
226
de171465
SM
227(defun doc-view-new-window-function (winprops)
228 (let ((ol (image-mode-window-get 'overlay winprops)))
229 (if ol
230 (setq ol (copy-overlay ol))
10b6e7c1 231 (assert (not (get-char-property (point-min) 'display)))
de171465
SM
232 (setq ol (make-overlay (point-min) (point-max) nil t))
233 (overlay-put ol 'doc-view t))
234 (overlay-put ol 'window (car winprops))
235 (image-mode-window-put 'overlay ol winprops)))
94dbe99c 236
de171465 237(defvar doc-view-current-files nil
94dbe99c 238 "Only used internally.")
ec4853ab 239(make-variable-buffer-local 'doc-view-current-files)
94dbe99c 240
ec4853ab 241(defvar doc-view-current-converter-processes nil
94dbe99c 242 "Only used internally.")
ec4853ab 243(make-variable-buffer-local 'doc-view-current-converter-processes)
94dbe99c
TTN
244
245(defvar doc-view-current-timer nil
246 "Only used internally.")
ec4853ab 247(make-variable-buffer-local 'doc-view-current-timer)
94dbe99c 248
94dbe99c
TTN
249(defvar doc-view-current-cache-dir nil
250 "Only used internally.")
ec4853ab 251(make-variable-buffer-local 'doc-view-current-cache-dir)
94dbe99c
TTN
252
253(defvar doc-view-current-search-matches nil
254 "Only used internally.")
ec4853ab 255(make-variable-buffer-local 'doc-view-current-search-matches)
94dbe99c 256
d99abf1b
RS
257(defvar doc-view-pending-cache-flush nil
258 "Only used internally.")
94dbe99c 259
937cb3fb 260(defvar doc-view-previous-major-mode nil
640602f7
RS
261 "Only used internally.")
262
39a402e3
TH
263(defvar doc-view-buffer-file-name nil
264 "Only used internally.
265The file name used for conversion. Normally it's the same as
266`buffer-file-name', but for remote files, compressed files and
267files inside an archive it is a temporary copy of
268the (uncompressed, extracted) file residing in
269`doc-view-cache-directory'.")
270
eb79098b
SM
271(defvar doc-view-doc-type nil
272 "The type of document in the current buffer.
273Can be `dvi', `pdf', or `ps'.")
274
640602f7 275;;;; DocView Keymaps
94dbe99c
TTN
276
277(defvar doc-view-mode-map
278 (let ((map (make-sparse-keymap)))
937cb3fb 279 (suppress-keymap map)
94dbe99c
TTN
280 ;; Navigation in the document
281 (define-key map (kbd "n") 'doc-view-next-page)
282 (define-key map (kbd "p") 'doc-view-previous-page)
eb8d0216
SM
283 (define-key map (kbd "<next>") 'forward-page)
284 (define-key map (kbd "<prior>") 'backward-page)
285 (define-key map [remap forward-page] 'doc-view-next-page)
286 (define-key map [remap backward-page] 'doc-view-previous-page)
94dbe99c
TTN
287 (define-key map (kbd "SPC") 'doc-view-scroll-up-or-next-page)
288 (define-key map (kbd "DEL") 'doc-view-scroll-down-or-previous-page)
289 (define-key map (kbd "M-<") 'doc-view-first-page)
290 (define-key map (kbd "M->") 'doc-view-last-page)
7656fe61 291 (define-key map [remap goto-line] 'doc-view-goto-page)
f1acd44c
TH
292 (define-key map [remap scroll-up] 'image-scroll-up)
293 (define-key map [remap scroll-down] 'image-scroll-down)
05477667
SM
294 ;; Zoom in/out.
295 (define-key map "+" 'doc-view-enlarge)
296 (define-key map "-" 'doc-view-shrink)
94dbe99c
TTN
297 ;; Killing/burying the buffer (and the process)
298 (define-key map (kbd "q") 'bury-buffer)
299 (define-key map (kbd "k") 'doc-view-kill-proc-and-buffer)
937cb3fb 300 (define-key map (kbd "K") 'doc-view-kill-proc)
94dbe99c
TTN
301 ;; Slicing the image
302 (define-key map (kbd "s s") 'doc-view-set-slice)
303 (define-key map (kbd "s m") 'doc-view-set-slice-using-mouse)
304 (define-key map (kbd "s r") 'doc-view-reset-slice)
305 ;; Searching
306 (define-key map (kbd "C-s") 'doc-view-search)
307 (define-key map (kbd "<find>") 'doc-view-search)
7baca0fa 308 (define-key map (kbd "C-r") 'doc-view-search-backward)
94dbe99c 309 ;; Scrolling
eb8d0216
SM
310 (define-key map [remap forward-char] 'image-forward-hscroll)
311 (define-key map [remap backward-char] 'image-backward-hscroll)
adc95ad6
TH
312 (define-key map [remap move-end-of-line] 'image-eol)
313 (define-key map [remap move-beginning-of-line] 'image-bol)
eb8d0216
SM
314 (define-key map [remap next-line] 'image-next-line)
315 (define-key map [remap previous-line] 'image-previous-line)
94dbe99c
TTN
316 ;; Show the tooltip
317 (define-key map (kbd "C-t") 'doc-view-show-tooltip)
640602f7
RS
318 ;; Toggle between text and image display or editing
319 (define-key map (kbd "C-c C-c") 'doc-view-toggle-display)
515357c2
TH
320 ;; Open a new buffer with doc's text contents
321 (define-key map (kbd "C-c C-t") 'doc-view-open-text)
640602f7 322 ;; Reconvert the current document
7656fe61
SM
323 (define-key map (kbd "g") 'revert-buffer)
324 (define-key map (kbd "r") 'revert-buffer)
94dbe99c 325 map)
640602f7
RS
326 "Keymap used by `doc-view-mode' when displaying a doc as a set of images.")
327
f4c75497
SM
328(easy-menu-define doc-view-menu doc-view-mode-map
329 "Menu for Doc View mode."
330 '("DocView"
331 ["Set Slice" doc-view-set-slice-using-mouse]
332 ["Set Slice (manual)" doc-view-set-slice]
333 ["Reset Slice" doc-view-reset-slice]
334 "---"
335 ["Search" doc-view-search]
7baca0fa 336 ["Search Backwards" doc-view-search-backward]
f4c75497 337 ["Toggle display" doc-view-toggle-display]
f4c75497
SM
338 ))
339
937cb3fb 340(defvar doc-view-minor-mode-map
640602f7
RS
341 (let ((map (make-sparse-keymap)))
342 ;; Toggle between text and image display or editing
343 (define-key map (kbd "C-c C-c") 'doc-view-toggle-display)
640602f7 344 map)
937cb3fb 345 "Keymap used by `doc-minor-view-mode'.")
94dbe99c
TTN
346
347;;;; Navigation Commands
348
160dfe43
SM
349(defmacro doc-view-current-page (&optional win)
350 `(image-mode-window-get 'page ,win))
de171465
SM
351(defmacro doc-view-current-info () `(image-mode-window-get 'info))
352(defmacro doc-view-current-overlay () `(image-mode-window-get 'overlay))
353(defmacro doc-view-current-image () `(image-mode-window-get 'image))
354(defmacro doc-view-current-slice () `(image-mode-window-get 'slice))
355
94dbe99c
TTN
356(defun doc-view-goto-page (page)
357 "View the page given by PAGE."
358 (interactive "nPage: ")
359 (let ((len (length doc-view-current-files)))
360 (if (< page 1)
1ca678aa 361 (setq page 1)
56741510
SM
362 (when (and (> page len)
363 ;; As long as the converter is running, we don't know
364 ;; how many pages will be available.
ec4853ab 365 (null doc-view-current-converter-processes))
1ca678aa 366 (setq page len)))
de171465
SM
367 (setf (doc-view-current-page) page
368 (doc-view-current-info)
1ca678aa
MC
369 (concat
370 (propertize
de171465 371 (format "Page %d of %d." page len) 'face 'bold)
1ca678aa 372 ;; Tell user if converting isn't finished yet
ec4853ab 373 (if doc-view-current-converter-processes
1ca678aa
MC
374 " (still converting...)\n"
375 "\n")
376 ;; Display context infos if this page matches the last search
377 (when (and doc-view-current-search-matches
de171465 378 (assq page doc-view-current-search-matches))
1ca678aa
MC
379 (concat (propertize "Search matches:\n" 'face 'bold)
380 (let ((contexts ""))
de171465 381 (dolist (m (cdr (assq page
1ca678aa
MC
382 doc-view-current-search-matches)))
383 (setq contexts (concat contexts " - \"" m "\"\n")))
384 contexts)))))
94dbe99c 385 ;; Update the buffer
56741510
SM
386 ;; We used to find the file name from doc-view-current-files but
387 ;; that's not right if the pages are not generated sequentially
388 ;; or if the page isn't in doc-view-current-files yet.
ec4853ab
SM
389 (let ((file (expand-file-name (format "page-%d.png" page)
390 (doc-view-current-cache-dir))))
391 (doc-view-insert-image file :pointer 'arrow)
392 (when (and (not (file-exists-p file))
393 doc-view-current-converter-processes)
394 ;; The PNG file hasn't been generated yet.
395 (doc-view-pdf->png-1 doc-view-buffer-file-name file page
396 (lexical-let ((page page)
397 (win (selected-window)))
398 (lambda ()
399 (and (eq (current-buffer) (window-buffer win))
400 ;; If we changed page in the mean
401 ;; time, don't mess things up.
402 (eq (doc-view-current-page win) page)
403 (with-selected-window win
404 (doc-view-goto-page page))))))))
de171465
SM
405 (overlay-put (doc-view-current-overlay)
406 'help-echo (doc-view-current-info))))
94dbe99c
TTN
407
408(defun doc-view-next-page (&optional arg)
409 "Browse ARG pages forward."
410 (interactive "p")
de171465 411 (doc-view-goto-page (+ (doc-view-current-page) (or arg 1))))
94dbe99c
TTN
412
413(defun doc-view-previous-page (&optional arg)
414 "Browse ARG pages backward."
415 (interactive "p")
de171465 416 (doc-view-goto-page (- (doc-view-current-page) (or arg 1))))
94dbe99c
TTN
417
418(defun doc-view-first-page ()
419 "View the first page."
420 (interactive)
421 (doc-view-goto-page 1))
422
423(defun doc-view-last-page ()
424 "View the last page."
425 (interactive)
426 (doc-view-goto-page (length doc-view-current-files)))
427
428(defun doc-view-scroll-up-or-next-page ()
429 "Scroll page up if possible, else goto next page."
430 (interactive)
89663176 431 (when (= (window-vscroll) (image-scroll-up nil))
de171465 432 (let ((cur-page (doc-view-current-page)))
bcc758ec 433 (doc-view-next-page)
de171465 434 (when (/= cur-page (doc-view-current-page))
d5793be3 435 (image-scroll-down nil)))))
94dbe99c
TTN
436
437(defun doc-view-scroll-down-or-previous-page ()
438 "Scroll page down if possible, else goto previous page."
439 (interactive)
89663176 440 (when (= (window-vscroll) (image-scroll-down nil))
de171465 441 (let ((cur-page (doc-view-current-page)))
bcc758ec 442 (doc-view-previous-page)
de171465 443 (when (/= cur-page (doc-view-current-page))
bcc758ec 444 (image-scroll-up nil)))))
94dbe99c 445
937cb3fb
GM
446;;;; Utility Functions
447
640602f7 448(defun doc-view-kill-proc ()
ec4853ab 449 "Kill the current converter process(es)."
640602f7 450 (interactive)
ec4853ab 451 (while doc-view-current-converter-processes
56741510 452 (ignore-errors ;; Maybe it's dead already?
ec4853ab 453 (kill-process (pop doc-view-current-converter-processes))))
640602f7
RS
454 (when doc-view-current-timer
455 (cancel-timer doc-view-current-timer)
456 (setq doc-view-current-timer nil))
457 (setq mode-line-process nil))
458
94dbe99c
TTN
459(defun doc-view-kill-proc-and-buffer ()
460 "Kill the current converter process and buffer."
461 (interactive)
640602f7 462 (doc-view-kill-proc)
94dbe99c 463 (when (eq major-mode 'doc-view-mode)
94dbe99c
TTN
464 (kill-buffer (current-buffer))))
465
c17587fe
SM
466(defun doc-view-make-safe-dir (dir)
467 (condition-case nil
468 (let ((umask (default-file-modes)))
469 (unwind-protect
470 (progn
471 ;; Create temp files with strict access rights. It's easy to
472 ;; loosen them later, whereas it's impossible to close the
473 ;; time-window of loose permissions otherwise.
474 (set-default-file-modes #o0700)
475 (make-directory dir))
476 ;; Reset the umask.
477 (set-default-file-modes umask)))
478 (file-already-exists
479 (if (file-symlink-p dir)
480 (error "Danger: %s points to a symbolic link" dir))
481 ;; In case it was created earlier with looser rights.
482 ;; We could check the mode info returned by file-attributes, but it's
483 ;; a pain to parse and it may not tell you what we want under
484 ;; non-standard file-systems. So let's just say what we want and let
485 ;; the underlying C code and file-system figure it out.
486 ;; This also ends up checking a bunch of useful conditions: it makes
487 ;; sure we have write-access to the directory and that we own it, thus
488 ;; closing a bunch of security holes.
489 (set-file-modes dir #o0700))))
490
937cb3fb
GM
491(defun doc-view-current-cache-dir ()
492 "Return the directory where the png files of the current doc should be saved.
493It's a subdirectory of `doc-view-cache-directory'."
494 (if doc-view-current-cache-dir
495 doc-view-current-cache-dir
c17587fe
SM
496 ;; Try and make sure doc-view-cache-directory exists and is safe.
497 (doc-view-make-safe-dir doc-view-cache-directory)
498 ;; Now compute the subdirectory to use.
937cb3fb
GM
499 (setq doc-view-current-cache-dir
500 (file-name-as-directory
c17587fe 501 (expand-file-name
39a402e3
TH
502 (concat (file-name-nondirectory buffer-file-name)
503 "-"
504 (let ((file doc-view-buffer-file-name))
505 (with-temp-buffer
86903c81 506 (set-buffer-multibyte nil)
39a402e3
TH
507 (insert-file-contents-literally file)
508 (md5 (current-buffer)))))
c17587fe 509 doc-view-cache-directory)))))
937cb3fb
GM
510
511(defun doc-view-remove-if (predicate list)
512 "Return LIST with all items removed that satisfy PREDICATE."
513 (let (new-list)
514 (dolist (item list (nreverse new-list))
515 (when (not (funcall predicate item))
516 (setq new-list (cons item new-list))))))
517
789ab9d4
RS
518;;;###autoload
519(defun doc-view-mode-p (type)
520 "Return non-nil if image type TYPE is available for `doc-view'.
521Image types are symbols like `dvi', `postscript' or `pdf'."
522 (and (display-graphic-p)
523 (image-type-available-p 'png)
524 (cond
525 ((eq type 'dvi)
526 (and (doc-view-mode-p 'pdf)
53d4c024
TH
527 (or (and doc-view-dvipdf-program
528 (executable-find doc-view-dvipdf-program))
529 (and doc-view-dvipdfm-program
530 (executable-find doc-view-dvipdfm-program)))))
622face2 531 ((or (eq type 'postscript) (eq type 'ps) (eq type 'eps)
789ab9d4
RS
532 (eq type 'pdf))
533 (and doc-view-ghostscript-program
534 (executable-find doc-view-ghostscript-program)))
535 (t ;; unknown image type
536 nil))))
537
94dbe99c
TTN
538;;;; Conversion Functions
539
05477667
SM
540(defvar doc-view-shrink-factor 1.125)
541
542(defun doc-view-enlarge (factor)
543 "Enlarge the document."
544 (interactive (list doc-view-shrink-factor))
545 (set (make-local-variable 'doc-view-resolution)
546 (* factor doc-view-resolution))
547 (doc-view-reconvert-doc))
548
549(defun doc-view-shrink (factor)
550 "Shrink the document."
551 (interactive (list doc-view-shrink-factor))
552 (doc-view-enlarge (/ 1.0 factor)))
553
c17587fe 554(defun doc-view-reconvert-doc ()
640602f7
RS
555 "Reconvert the current document.
556Should be invoked when the cached images aren't up-to-date."
557 (interactive)
f4c75497
SM
558 (doc-view-kill-proc)
559 ;; Clear the old cached files
560 (when (file-exists-p (doc-view-current-cache-dir))
561 (dired-delete-file (doc-view-current-cache-dir) 'always))
c17587fe 562 (doc-view-initiate-display))
94dbe99c 563
b4cb319f
SM
564(defun doc-view-sentinel (proc event)
565 "Generic sentinel for doc-view conversion processes."
94dbe99c 566 (if (not (string-match "finished" event))
b4cb319f
SM
567 (message "DocView: process %s changed status to %s."
568 (process-name proc) event)
ec4853ab
SM
569 (when (buffer-live-p (process-get proc 'buffer))
570 (with-current-buffer (process-get proc 'buffer)
571 (setq doc-view-current-converter-processes
572 (delq proc doc-view-current-converter-processes))
573 (setq mode-line-process
574 (if doc-view-current-converter-processes
575 (format ":%s" (car doc-view-current-converter-processes))))
576 (funcall (process-get proc 'callback))))))
577
578(defun doc-view-start-process (name program args callback)
3c03f2ce
TH
579 ;; Make sure the process is started in an existing directory, (rather than
580 ;; some file-name-handler-managed dir, for example).
581 (let* ((default-directory (if (file-readable-p default-directory)
582 default-directory
583 (expand-file-name "~/")))
ec4853ab
SM
584 (proc (apply 'start-process name doc-view-conversion-buffer
585 program args)))
586 (push proc doc-view-current-converter-processes)
587 (setq mode-line-process (list (format ":%s" proc)))
588 (set-process-sentinel proc 'doc-view-sentinel)
589 (process-put proc 'buffer (current-buffer))
590 (process-put proc 'callback callback)))
b4cb319f
SM
591
592(defun doc-view-dvi->pdf (dvi pdf callback)
593 "Convert DVI to PDF asynchronously and call CALLBACK when finished."
53d4c024
TH
594 ;; Prefer dvipdf over dvipdfm, because the latter has problems if the DVI
595 ;; references and includes other PS files.
596 (if (and doc-view-dvipdf-program
597 (executable-find doc-view-dvipdf-program))
598 (doc-view-start-process "dvi->pdf" doc-view-dvipdf-program
599 (list dvi pdf)
600 callback)
601 (doc-view-start-process "dvi->pdf" doc-view-dvipdfm-program
602 (list "-o" pdf dvi)
603 callback)))
ec4853ab 604
94dbe99c
TTN
605
606(defun doc-view-pdf/ps->png (pdf-ps png)
1f75e53d 607 "Convert PDF-PS to PNG asynchronously."
ec4853ab
SM
608 (doc-view-start-process
609 "pdf/ps->png" doc-view-ghostscript-program
610 (append doc-view-ghostscript-options
611 (list (format "-r%d" (round doc-view-resolution))
612 (concat "-sOutputFile=" png)
613 pdf-ps))
3c03f2ce 614 (lambda ()
ec4853ab
SM
615 (when doc-view-current-timer
616 (cancel-timer doc-view-current-timer)
617 (setq doc-view-current-timer nil))
618 (doc-view-display (current-buffer) 'force)))
619 ;; Update the displayed pages as soon as they're done generating.
94dbe99c
TTN
620 (when doc-view-conversion-refresh-interval
621 (setq doc-view-current-timer
ec4853ab
SM
622 (run-at-time "1 secs" doc-view-conversion-refresh-interval
623 'doc-view-display
624 (current-buffer)))))
625
626(defun doc-view-pdf->png-1 (pdf png page callback)
627 "Convert a PAGE of a PDF file to PNG asynchronously.
628Call CALLBACK with no arguments when done."
629 (doc-view-start-process
630 "pdf->png-1" doc-view-ghostscript-program
631 (append doc-view-ghostscript-options
632 (list (format "-r%d" (round doc-view-resolution))
633 ;; Sadly, `gs' only supports the page-range
634 ;; for PDF files.
635 (format "-dFirstPage=%d" page)
636 (format "-dLastPage=%d" page)
637 (concat "-sOutputFile=" png)
638 pdf))
639 callback))
640
641(defun doc-view-pdf->png (pdf png pages)
642 "Convert a PDF file to PNG asynchronously.
643Start by converting PAGES, and then the rest."
644 (if (null pages)
645 (doc-view-pdf/ps->png pdf png)
646 ;; We could render several `pages' with a single process if they're
647 ;; (almost) consecutive, but since in 99% of the cases, there'll be only
648 ;; a single page anyway, and of the remaining 1%, few cases will have
649 ;; consecutive pages, it's not worth the trouble.
650 (lexical-let ((pdf pdf) (png png) (rest (cdr pages)))
651 (doc-view-pdf->png-1
652 pdf (format png (car pages)) (car pages)
653 (lambda ()
654 (if rest
655 (doc-view-pdf->png pdf png rest)
656 ;; Yippie, the important pages are done, update the display.
657 (clear-image-cache)
658 ;; Convert the rest of the pages.
659 (doc-view-pdf/ps->png pdf png)))))))
94dbe99c 660
b4cb319f
SM
661(defun doc-view-pdf->txt (pdf txt callback)
662 "Convert PDF to TXT asynchronously and call CALLBACK when finished."
ec4853ab
SM
663 (doc-view-start-process "pdf->txt" doc-view-pdftotext-program
664 (list "-raw" pdf txt)
665 callback))
515357c2 666
b4cb319f
SM
667(defun doc-view-doc->txt (txt callback)
668 "Convert the current document to text and call CALLBACK when done."
7edd6b92 669 (make-directory (doc-view-current-cache-dir) t)
b4cb319f
SM
670 (case doc-view-doc-type
671 (pdf
672 ;; Doc is a PDF, so convert it to TXT
673 (doc-view-pdf->txt doc-view-buffer-file-name txt callback))
674 (ps
675 ;; Doc is a PS, so convert it to PDF (which will be converted to
676 ;; TXT thereafter).
677 (lexical-let ((pdf (expand-file-name "doc.pdf"
678 (doc-view-current-cache-dir)))
679 (txt txt)
680 (callback callback))
681 (doc-view-ps->pdf doc-view-buffer-file-name pdf
682 (lambda () (doc-view-pdf->txt pdf txt callback)))))
683 (dvi
684 ;; Doc is a DVI. This means that a doc.pdf already exists in its
685 ;; cache subdirectory.
686 (doc-view-pdf->txt (expand-file-name "doc.pdf"
687 (doc-view-current-cache-dir))
688 txt callback))
689 (t (error "DocView doesn't know what to do"))))
690
691(defun doc-view-ps->pdf (ps pdf callback)
692 "Convert PS to PDF asynchronously and call CALLBACK when finished."
ec4853ab
SM
693 (doc-view-start-process "ps->pdf" doc-view-ps2pdf-program
694 (list
695 ;; Avoid security problems when rendering files from
696 ;; untrusted sources.
697 "-dSAFER"
698 ;; in-file and out-file
699 ps pdf)
700 callback))
701
702(defun doc-view-active-pages ()
703 (let ((pages ()))
704 (dolist (win (get-buffer-window-list (current-buffer) nil 'visible))
705 (let ((page (image-mode-window-get 'page win)))
706 (unless (memq page pages) (push page pages))))
707 pages))
94dbe99c 708
640602f7 709(defun doc-view-convert-current-doc ()
39a402e3 710 "Convert `doc-view-buffer-file-name' to a set of png files, one file per page.
640602f7
RS
711Those files are saved in the directory given by the function
712`doc-view-current-cache-dir'."
c17587fe
SM
713 ;; Let stale files still display while we recompute the new ones, so only
714 ;; flush the cache when the conversion is over. One of the reasons why it
715 ;; is important to keep displaying the stale page is so that revert-buffer
716 ;; preserves the horizontal/vertical scroll settings (which are otherwise
717 ;; resets during the redisplay).
718 (setq doc-view-pending-cache-flush t)
719 (let ((png-file (expand-file-name "page-%d.png"
720 (doc-view-current-cache-dir))))
7edd6b92 721 (make-directory (doc-view-current-cache-dir) t)
eb79098b
SM
722 (case doc-view-doc-type
723 (dvi
724 ;; DVI files have to be converted to PDF before Ghostscript can process
725 ;; it.
b4cb319f
SM
726 (lexical-let
727 ((pdf (expand-file-name "doc.pdf" doc-view-current-cache-dir))
728 (png-file png-file))
729 (doc-view-dvi->pdf doc-view-buffer-file-name pdf
730 (lambda () (doc-view-pdf/ps->png pdf png-file)))))
ec4853ab
SM
731 (pdf
732 (let ((pages (doc-view-active-pages)))
733 ;; Convert PDF to PNG images starting with the active pages.
734 (doc-view-pdf->png doc-view-buffer-file-name png-file pages)))
eb79098b
SM
735 (t
736 ;; Convert to PNG images.
737 (doc-view-pdf/ps->png doc-view-buffer-file-name png-file)))))
94dbe99c 738
94dbe99c
TTN
739;;;; Slicing
740
741(defun doc-view-set-slice (x y width height)
742 "Set the slice of the images that should be displayed.
743You can use this function to tell doc-view not to display the
744margins of the document. It prompts for the top-left corner (X
745and Y) of the slice to display and its WIDTH and HEIGHT.
746
747See `doc-view-set-slice-using-mouse' for a more convenient way to
748do that. To reset the slice use `doc-view-reset-slice'."
749 (interactive
de171465 750 (let* ((size (image-size (doc-view-current-image) t))
1ca678aa
MC
751 (a (read-number (format "Top-left X (0..%d): " (car size))))
752 (b (read-number (format "Top-left Y (0..%d): " (cdr size))))
753 (c (read-number (format "Width (0..%d): " (- (car size) a))))
754 (d (read-number (format "Height (0..%d): " (- (cdr size) b)))))
94dbe99c 755 (list a b c d)))
de171465 756 (setf (doc-view-current-slice) (list x y width height))
94dbe99c 757 ;; Redisplay
de171465 758 (doc-view-goto-page (doc-view-current-page)))
94dbe99c
TTN
759
760(defun doc-view-set-slice-using-mouse ()
761 "Set the slice of the images that should be displayed.
762You set the slice by pressing mouse-1 at its top-left corner and
763dragging it to its bottom-right corner. See also
764`doc-view-set-slice' and `doc-view-reset-slice'."
765 (interactive)
766 (let (x y w h done)
767 (while (not done)
768 (let ((e (read-event
1ca678aa
MC
769 (concat "Press mouse-1 at the top-left corner and "
770 "drag it to the bottom-right corner!"))))
771 (when (eq (car e) 'drag-mouse-1)
772 (setq x (car (posn-object-x-y (event-start e))))
773 (setq y (cdr (posn-object-x-y (event-start e))))
774 (setq w (- (car (posn-object-x-y (event-end e))) x))
775 (setq h (- (cdr (posn-object-x-y (event-end e))) y))
776 (setq done t))))
94dbe99c
TTN
777 (doc-view-set-slice x y w h)))
778
779(defun doc-view-reset-slice ()
e48a5bf9 780 "Reset the current slice.
1f75e53d 781After calling this function whole pages will be visible again."
94dbe99c 782 (interactive)
de171465 783 (setf (doc-view-current-slice) nil)
94dbe99c 784 ;; Redisplay
de171465 785 (doc-view-goto-page (doc-view-current-page)))
94dbe99c
TTN
786
787;;;; Display
788
789(defun doc-view-insert-image (file &rest args)
790 "Insert the given png FILE.
e48a5bf9 791ARGS is a list of image descriptors."
c17587fe
SM
792 (when doc-view-pending-cache-flush
793 (clear-image-cache)
794 (setq doc-view-pending-cache-flush nil))
de171465 795 (let ((ol (doc-view-current-overlay))
56741510
SM
796 (image (if (and file (file-readable-p file))
797 (apply 'create-image file 'png nil args)))
de171465
SM
798 (slice (doc-view-current-slice)))
799 (setf (doc-view-current-image) image)
56741510 800 (move-overlay ol (point-min) (point-max))
de171465 801 (overlay-put ol 'display
56741510
SM
802 (cond
803 (image
de171465
SM
804 (if slice
805 (list (cons 'slice slice) image)
56741510
SM
806 image))
807 ;; We're trying to display a page that doesn't exist.
ec4853ab 808 (doc-view-current-converter-processes
56741510
SM
809 ;; Maybe the page doesn't exist *yet*.
810 "Cannot display this page (yet)!")
811 (t
812 ;; Typically happens if the conversion process somehow
813 ;; failed. Better not signal an error here because it
814 ;; could prevent a subsequent reconversion from fixing
815 ;; the problem.
816 (concat "Cannot display this page!\n"
817 "Maybe because of a conversion failure!"))))
818 (let ((win (overlay-get ol 'window)))
819 (if (stringp (overlay-get ol 'display))
820 (progn ;Make sure the text is not scrolled out of view.
821 (set-window-hscroll win 0)
822 (set-window-vscroll win 0))
823 (let ((hscroll (image-mode-window-get 'hscroll win))
824 (vscroll (image-mode-window-get 'vscroll win)))
825 ;; Reset scroll settings, in case they were changed.
826 (if hscroll (set-window-hscroll win hscroll))
827 (if vscroll (set-window-vscroll win vscroll)))))))
94dbe99c
TTN
828
829(defun doc-view-sort (a b)
830 "Return non-nil if A should be sorted before B.
831Predicate for sorting `doc-view-current-files'."
f4c75497
SM
832 (or (< (length a) (length b))
833 (and (= (length a) (length b))
834 (string< a b))))
94dbe99c 835
65073003
SM
836(defun doc-view-display (buffer &optional force)
837 "Start viewing the document in BUFFER.
214abdd4
SM
838If FORCE is non-nil, start viewing even if the document does not
839have the page we want to view."
65073003 840 (with-current-buffer buffer
56741510
SM
841 (let ((prev-pages doc-view-current-files))
842 (setq doc-view-current-files
843 (sort (directory-files (doc-view-current-cache-dir) t
844 "page-[0-9]+\\.png" t)
845 'doc-view-sort))
10b6e7c1
CY
846 (dolist (win (or (get-buffer-window-list buffer nil t)
847 (list (selected-window))))
848 (let* ((page (doc-view-current-page win))
849 (pagefile (expand-file-name (format "page-%d.png" page)
850 (doc-view-current-cache-dir))))
851 (when (or force
852 (and (not (member pagefile prev-pages))
853 (member pagefile doc-view-current-files)))
854 (with-selected-window win
855 (assert (eq (current-buffer) buffer))
856 (doc-view-goto-page page))))))))
94dbe99c
TTN
857
858(defun doc-view-buffer-message ()
c17587fe
SM
859 ;; Only show this message initially, not when refreshing the buffer (in which
860 ;; case it's better to keep displaying the "stale" page while computing
861 ;; the fresh new ones).
de171465
SM
862 (unless (overlay-get (doc-view-current-overlay) 'display)
863 (overlay-put (doc-view-current-overlay) 'display
c17587fe
SM
864 (concat (propertize "Welcome to DocView!" 'face 'bold)
865 "\n"
866 "
1f75e53d 867If you see this buffer it means that the document you want to view is being
f4c75497 868converted to PNG and the conversion of the first page hasn't finished yet or
94dbe99c
TTN
869`doc-view-conversion-refresh-interval' is set to nil.
870
871For now these keys are useful:
872
1ca678aa 873`q' : Bury this buffer. Conversion will go on in background.
937cb3fb 874`k' : Kill the conversion process and this buffer.
c17587fe 875`K' : Kill the conversion process.\n"))))
94dbe99c
TTN
876
877(defun doc-view-show-tooltip ()
878 (interactive)
de171465 879 (tooltip-show (doc-view-current-info)))
94dbe99c 880
515357c2
TH
881(defun doc-view-open-text ()
882 "Open a buffer with the current doc's contents as text."
883 (interactive)
ec4853ab 884 (if doc-view-current-converter-processes
515357c2
TH
885 (message "DocView: please wait till conversion finished.")
886 (let ((txt (expand-file-name "doc.txt" (doc-view-current-cache-dir))))
887 (if (file-readable-p txt)
888 (find-file txt)
b4cb319f 889 (doc-view-doc->txt txt 'doc-view-open-text)))))
515357c2 890
937cb3fb 891;;;;; Toggle between editing and viewing
640602f7 892
515357c2 893
640602f7 894(defun doc-view-toggle-display ()
937cb3fb 895 "Toggle between editing a document as text or viewing it."
640602f7 896 (interactive)
937cb3fb
GM
897 (if (eq major-mode 'doc-view-mode)
898 ;; Switch to editing mode
899 (progn
900 (doc-view-kill-proc)
901 (setq buffer-read-only nil)
515357c2
TH
902 (remove-overlays (point-min) (point-max) 'doc-view t)
903 (set (make-local-variable 'image-mode-winprops-alist) t)
937cb3fb
GM
904 ;; Switch to the previously used major mode or fall back to fundamental
905 ;; mode.
906 (if doc-view-previous-major-mode
907 (funcall doc-view-previous-major-mode)
908 (fundamental-mode))
c17587fe 909 (doc-view-minor-mode 1))
937cb3fb
GM
910 ;; Switch to doc-view-mode
911 (when (and (buffer-modified-p)
912 (y-or-n-p "The buffer has been modified. Save the changes? "))
913 (save-buffer))
937cb3fb 914 (doc-view-mode)))
640602f7 915
94dbe99c
TTN
916;;;; Searching
917
515357c2 918
94dbe99c
TTN
919(defun doc-view-search-internal (regexp file)
920 "Return a list of FILE's pages that contain text matching REGEXP.
1ca678aa
MC
921The value is an alist of the form (PAGE CONTEXTS) where PAGE is
922the pagenumber and CONTEXTS are all lines of text containing a match."
94dbe99c
TTN
923 (with-temp-buffer
924 (insert-file-contents file)
925 (let ((page 1)
1ca678aa
MC
926 (lastpage 1)
927 matches)
94dbe99c 928 (while (re-search-forward (concat "\\(?:\\([\f]\\)\\|\\("
1ca678aa 929 regexp "\\)\\)") nil t)
069b4ce3 930 (when (match-string 1) (setq page (1+ page)))
1ca678aa
MC
931 (when (match-string 2)
932 (if (/= page lastpage)
c17587fe 933 (push (cons page
515357c2
TH
934 (list (buffer-substring
935 (line-beginning-position)
936 (line-end-position))))
937 matches)
1ca678aa
MC
938 (setq matches (cons
939 (append
940 (or
941 ;; This page already is a match.
942 (car matches)
943 ;; This is the first match on page.
944 (list page))
945 (list (buffer-substring
946 (line-beginning-position)
947 (line-end-position))))
948 (cdr matches))))
949 (setq lastpage page)))
94dbe99c
TTN
950 (nreverse matches))))
951
952(defun doc-view-search-no-of-matches (list)
953 "Extract the number of matches from the search result LIST."
954 (let ((no 0))
955 (dolist (p list)
956 (setq no (+ no (1- (length p)))))
957 no))
958
7baca0fa
JL
959(defun doc-view-search-backward (new-query)
960 "Call `doc-view-search' for backward search.
961If prefix NEW-QUERY is given, ask for a new regexp."
962 (interactive "P")
da99b369 963 (doc-view-search new-query t))
7baca0fa
JL
964
965(defun doc-view-search (new-query &optional backward)
966 "Jump to the next match or initiate a new search if NEW-QUERY is given.
94dbe99c 967If the current document hasn't been transformed to plain text
7baca0fa
JL
968till now do that first.
969If BACKWARD is non-nil, jump to the previous match."
970 (interactive "P")
da99b369 971 (if (and (not new-query)
7baca0fa
JL
972 doc-view-current-search-matches)
973 (if backward
974 (doc-view-search-previous-match 1)
975 (doc-view-search-next-match 1))
976 ;; New search, so forget the old results.
977 (setq doc-view-current-search-matches nil)
978 (let ((txt (expand-file-name "doc.txt"
979 (doc-view-current-cache-dir))))
980 (if (file-readable-p txt)
981 (progn
982 (setq doc-view-current-search-matches
983 (doc-view-search-internal
984 (read-from-minibuffer "Regexp: ")
985 txt))
986 (message "DocView: search yielded %d matches."
987 (doc-view-search-no-of-matches
988 doc-view-current-search-matches)))
989 ;; We must convert to TXT first!
ec4853ab 990 (if doc-view-current-converter-processes
7baca0fa 991 (message "DocView: please wait till conversion finished.")
b4cb319f 992 (doc-view-doc->txt txt (lambda () (doc-view-search nil))))))))
94dbe99c
TTN
993
994(defun doc-view-search-next-match (arg)
995 "Go to the ARGth next matching page."
996 (interactive "p")
937cb3fb 997 (let* ((next-pages (doc-view-remove-if
de171465 998 (lambda (i) (<= (car i) (doc-view-current-page)))
937cb3fb 999 doc-view-current-search-matches))
1ca678aa 1000 (page (car (nth (1- arg) next-pages))))
94dbe99c 1001 (if page
1ca678aa 1002 (doc-view-goto-page page)
94dbe99c 1003 (when (and
1ca678aa
MC
1004 doc-view-current-search-matches
1005 (y-or-n-p "No more matches after current page. Wrap to first match? "))
1006 (doc-view-goto-page (caar doc-view-current-search-matches))))))
94dbe99c
TTN
1007
1008(defun doc-view-search-previous-match (arg)
1009 "Go to the ARGth previous matching page."
1010 (interactive "p")
937cb3fb 1011 (let* ((prev-pages (doc-view-remove-if
de171465 1012 (lambda (i) (>= (car i) (doc-view-current-page)))
937cb3fb 1013 doc-view-current-search-matches))
1ca678aa 1014 (page (car (nth (1- arg) (nreverse prev-pages)))))
94dbe99c 1015 (if page
1ca678aa 1016 (doc-view-goto-page page)
94dbe99c 1017 (when (and
1ca678aa
MC
1018 doc-view-current-search-matches
1019 (y-or-n-p "No more matches before current page. Wrap to last match? "))
1020 (doc-view-goto-page (caar (last doc-view-current-search-matches)))))))
94dbe99c 1021
640602f7 1022;;;; User interface commands and the mode
94dbe99c 1023
c17587fe
SM
1024;; (put 'doc-view-mode 'mode-class 'special)
1025
515357c2
TH
1026(defun doc-view-already-converted-p ()
1027 "Return non-nil if the current doc was already converted."
1028 (and (file-exists-p (doc-view-current-cache-dir))
1ecc9da7 1029 (> (length (directory-files (doc-view-current-cache-dir) nil "\\.png$")) 0)))
515357c2 1030
c17587fe
SM
1031(defun doc-view-initiate-display ()
1032 ;; Switch to image display if possible
322f4559 1033 (if (doc-view-mode-p doc-view-doc-type)
c17587fe
SM
1034 (progn
1035 (doc-view-buffer-message)
de171465 1036 (setf (doc-view-current-page) (or (doc-view-current-page) 1))
515357c2 1037 (if (doc-view-already-converted-p)
c17587fe
SM
1038 (progn
1039 (message "DocView: using cached files!")
65073003 1040 (doc-view-display (current-buffer) 'force))
c17587fe
SM
1041 (doc-view-convert-current-doc))
1042 (message
1043 "%s"
1044 (substitute-command-keys
1045 (concat "Type \\[doc-view-toggle-display] to toggle between "
1046 "editing or viewing the document."))))
1047 (message
1048 "%s"
1049 (substitute-command-keys
d99abf1b 1050 (concat "No image (png) support available or some conversion utility for "
39a402e3 1051 (file-name-extension doc-view-buffer-file-name)" files is missing. "
515357c2
TH
1052 "Type \\[doc-view-toggle-display] to switch to an editing mode or "
1053 "\\[doc-view-open-text] to open a buffer showing the doc as text.")))))
94dbe99c 1054
e0385bf4 1055(defvar bookmark-make-record-function)
069b4ce3 1056
65073003
SM
1057(defun doc-view-clone-buffer-hook ()
1058 ;; FIXME: There are several potential problems linked with reconversion
1059 ;; and auto-revert when we have indirect buffers because they share their
1060 ;; /tmp cache directory. This sharing is good (you'd rather not reconvert
1061 ;; for each clone), but that means that clones need to collaborate a bit.
1062 ;; I guess it mostly means: detect when a reconversion process is already
1063 ;; running, and run the sentinel in all clones.
515357c2 1064 ;;
de171465 1065 ;; Maybe the clones should really have a separate /tmp directory
65073003
SM
1066 ;; so they could have a different resolution and you could use clones
1067 ;; for zooming.
de171465
SM
1068 (remove-overlays (point-min) (point-max) 'doc-view t)
1069 (if (consp image-mode-winprops-alist) (setq image-mode-winprops-alist nil)))
65073003 1070
eb79098b
SM
1071(defun doc-view-intersection (l1 l2)
1072 (let ((l ()))
1073 (dolist (x l1) (if (memq x l2) (push x l)))
1074 l))
1075
640602f7 1076;;;###autoload
937cb3fb 1077(defun doc-view-mode ()
640602f7
RS
1078 "Major mode in DocView buffers.
1079You can use \\<doc-view-mode-map>\\[doc-view-toggle-display] to
5c1f16b0
SM
1080toggle between displaying the document or editing it as text.
1081\\{doc-view-mode-map}"
937cb3fb 1082 (interactive)
f9adf05b
TH
1083
1084 (let* ((prev-major-mode (if (eq major-mode 'doc-view-mode)
1085 doc-view-previous-major-mode
1086 major-mode)))
1087 (kill-all-local-variables)
1088 (set (make-local-variable 'doc-view-previous-major-mode) prev-major-mode))
1089
eb79098b
SM
1090 ;; Figure out the document type.
1091 (let ((name-types
1092 (when buffer-file-name
1093 (cdr (assoc (file-name-extension buffer-file-name)
1094 '(("dvi" dvi)
1095 ("pdf" pdf)
1096 ("epdf" pdf)
1097 ("ps" ps)
1098 ("eps" ps))))))
1099 (content-types
1100 (save-excursion
1101 (goto-char (point-min))
1102 (cond
1103 ((looking-at "%!") '(ps))
1104 ((looking-at "%PDF") '(pdf))
1105 ((looking-at "\367\002") '(dvi))))))
1106 (set (make-local-variable 'doc-view-doc-type)
1107 (car (or (doc-view-intersection name-types content-types)
1108 (when (and name-types content-types)
1109 (error "Conflicting types: name says %s but content says %s"
1110 name-types content-types))
1111 name-types content-types
1112 (error "Cannot determine the document type")))))
5986c5c6 1113
56741510 1114 (doc-view-make-safe-dir doc-view-cache-directory)
39a402e3
TH
1115 ;; Handle compressed files, remote files, files inside archives
1116 (set (make-local-variable 'doc-view-buffer-file-name)
1117 (cond
1118 (jka-compr-really-do-compress
1119 (expand-file-name
1120 (file-name-nondirectory
1121 (file-name-sans-extension buffer-file-name))
1122 doc-view-cache-directory))
cec1df02
SM
1123 ;; Is the file readable by local processes?
1124 ;; We used to use `file-remote-p' but it's unclear what it's
1125 ;; supposed to return nil for things like local files accessed via
1126 ;; `su' or via file://...
1127 ((let ((file-name-handler-alist nil))
1128 (not (file-readable-p buffer-file-name)))
39a402e3
TH
1129 (expand-file-name
1130 (file-name-nondirectory buffer-file-name)
1131 doc-view-cache-directory))
1132 (t buffer-file-name)))
1133 (when (not (string= doc-view-buffer-file-name buffer-file-name))
1134 (write-region nil nil doc-view-buffer-file-name))
1135
f9adf05b 1136 (add-hook 'change-major-mode-hook
ec4853ab
SM
1137 (lambda ()
1138 (doc-view-kill-proc)
1139 (remove-overlays (point-min) (point-max) 'doc-view t))
f9adf05b 1140 nil t)
65073003 1141 (add-hook 'clone-indirect-buffer-hook 'doc-view-clone-buffer-hook nil t)
ec4853ab 1142 (add-hook 'kill-buffer 'doc-view-kill-proc nil t)
6f70aa33 1143
de171465
SM
1144 (remove-overlays (point-min) (point-max) 'doc-view t) ;Just in case.
1145 ;; Keep track of display info ([vh]scroll, page number, overlay, ...)
1146 ;; for each window in which this document is shown.
1147 (add-hook 'image-mode-new-window-functions
1148 'doc-view-new-window-function nil t)
1149 (image-mode-setup-winprops)
515357c2 1150
f9adf05b 1151 (set (make-local-variable 'mode-line-position)
de171465 1152 '(" P" (:eval (number-to-string (doc-view-current-page)))
f9adf05b 1153 "/" (:eval (number-to-string (length doc-view-current-files)))))
38b5ca4a
SM
1154 ;; Don't scroll unless the user specifically asked for it.
1155 (set (make-local-variable 'auto-hscroll-mode) nil)
f9adf05b
TH
1156 (set (make-local-variable 'cursor-type) nil)
1157 (use-local-map doc-view-mode-map)
1158 (set (make-local-variable 'after-revert-hook) 'doc-view-reconvert-doc)
e0385bf4
KF
1159 (set (make-local-variable 'bookmark-make-record-function)
1160 'doc-view-bookmark-make-record)
f9adf05b
TH
1161 (setq mode-name "DocView"
1162 buffer-read-only t
1163 major-mode 'doc-view-mode)
1164 (doc-view-initiate-display)
1165 (run-mode-hooks 'doc-view-mode-hook))
937cb3fb
GM
1166
1167;;;###autoload
1168(define-minor-mode doc-view-minor-mode
1169 "Toggle Doc view minor mode.
1170With arg, turn Doc view minor mode on if arg is positive, off otherwise.
1171See the command `doc-view-mode' for more information on this mode."
1172 nil " DocView" doc-view-minor-mode-map
1173 :group 'doc-view
1174 (when doc-view-minor-mode
1175 (add-hook 'change-major-mode-hook (lambda () (doc-view-minor-mode -1)) nil t)
1176 (message
1177 "%s"
1178 (substitute-command-keys
1179 "Type \\[doc-view-toggle-display] to toggle between editing or viewing the document."))))
94dbe99c
TTN
1180
1181(defun doc-view-clear-cache ()
1182 "Delete the whole cache (`doc-view-cache-directory')."
1183 (interactive)
515357c2 1184 (dired-delete-file doc-view-cache-directory 'always))
94dbe99c
TTN
1185
1186(defun doc-view-dired-cache ()
1187 "Open `dired' in `doc-view-cache-directory'."
1188 (interactive)
1189 (dired doc-view-cache-directory))
1190
94dbe99c 1191
1666a6b3
TH
1192;;;; Bookmark integration
1193
a9f8b49b
SM
1194(declare-function bookmark-buffer-file-name "bookmark" ())
1195(declare-function bookmark-prop-get "bookmark" (bookmark prop))
1196
32a091dd 1197(defun doc-view-bookmark-make-record ()
a9f8b49b 1198 `((filename . ,(bookmark-buffer-file-name))
32a091dd
SM
1199 (page . ,(doc-view-current-page))
1200 (handler . doc-view-bookmark-jump)))
1666a6b3 1201
069b4ce3 1202
1666a6b3
TH
1203;;;###autoload
1204(defun doc-view-bookmark-jump (bmk)
03e26a79 1205 ;; This implements the `handler' function interface for record type
e0385bf4 1206 ;; returned by `doc-view-bookmark-make-record', which see.
a9f8b49b
SM
1207 (let ((filename (bookmark-prop-get bmk 'filename))
1208 (page (bookmark-prop-get bmk 'page)))
1209 (with-current-buffer (find-file-noselect filename)
1666a6b3
TH
1210 (when (not (eq major-mode 'doc-view-mode))
1211 (doc-view-toggle-display))
a9f8b49b
SM
1212 (with-selected-window
1213 (or (get-buffer-window (current-buffer) 0)
1214 (selected-window))
1215 (doc-view-goto-page page))
03e26a79 1216 `((buffer ,(current-buffer)) (position ,1)))))
1666a6b3 1217
069b4ce3
GM
1218
1219(provide 'doc-view)
1220
1221;; Local Variables:
1222;; mode: outline-minor
1223;; End:
1224
aab2236f 1225;; arch-tag: 5d6e5c5e-095f-489e-b4e4-1ca90a7d79be
94dbe99c 1226;;; doc-view.el ends here