Add 2011 to FSF/AIST copyright years.
[bpt/emacs.git] / lisp / doc-view.el
CommitLineData
94dbe99c
TTN
1;;; doc-view.el --- View PDF/PostScript/DVI files in Emacs
2
5df4f04c 3;; Copyright (C) 2007, 2008, 2009, 2010, 2011 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
a8ce3d17 65;; `doc-view-set-slice-using-mouse' (bound to `s m'). After invocation you
94dbe99c
TTN
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
a8ce3d17 77;; that doesn't already exist the first invocation of `doc-view-search' (or
7baca0fa
JL
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
ff90f4b0 147 :group 'data
94dbe99c
TTN
148 :group 'multimedia
149 :prefix "doc-view-")
150
c9a9a5e3 151(defcustom doc-view-ghostscript-program (executable-find "gs")
94dbe99c 152 "Program to convert PS and PDF files to PNG."
c9a9a5e3 153 :type 'file
94dbe99c
TTN
154 :group 'doc-view)
155
156(defcustom doc-view-ghostscript-options
6a658a30
RS
157 '("-dSAFER" ;; Avoid security problems when rendering files from untrusted
158 ;; sources.
159 "-dNOPAUSE" "-sDEVICE=png16m" "-dTextAlphaBits=4"
05477667 160 "-dBATCH" "-dGraphicsAlphaBits=4" "-dQUIET")
4b378e75 161 "A list of options to give to ghostscript."
c9a9a5e3 162 :type '(repeat string)
94dbe99c
TTN
163 :group 'doc-view)
164
05477667
SM
165(defcustom doc-view-resolution 100
166 "Dots per inch resolution used to render the documents.
167Higher values result in larger images."
9efa445f
DN
168 :type 'number
169 :group 'doc-view)
05477667 170
c9a9a5e3 171(defcustom doc-view-dvipdfm-program (executable-find "dvipdfm")
94dbe99c
TTN
172 "Program to convert DVI files to PDF.
173
174DVI file will be converted to PDF before the resulting PDF is
53d4c024
TH
175converted to PNG.
176
177If this and `doc-view-dvipdf-program' are set,
178`doc-view-dvipdf-program' will be preferred."
179 :type 'file
180 :group 'doc-view)
181
182(defcustom doc-view-dvipdf-program (executable-find "dvipdf")
183 "Program to convert DVI files to PDF.
184
185DVI file will be converted to PDF before the resulting PDF is
186converted to PNG.
187
188If this and `doc-view-dvipdfm-program' are set,
189`doc-view-dvipdf-program' will be preferred."
c9a9a5e3 190 :type 'file
94dbe99c
TTN
191 :group 'doc-view)
192
c9a9a5e3 193(defcustom doc-view-ps2pdf-program (executable-find "ps2pdf")
94dbe99c
TTN
194 "Program to convert PS files to PDF.
195
196PS files will be converted to PDF before searching is possible."
c9a9a5e3 197 :type 'file
94dbe99c
TTN
198 :group 'doc-view)
199
c9a9a5e3 200(defcustom doc-view-pdftotext-program (executable-find "pdftotext")
94dbe99c
TTN
201 "Program to convert PDF files to plain text.
202
203Needed for searching."
c9a9a5e3 204 :type 'file
94dbe99c
TTN
205 :group 'doc-view)
206
c17587fe 207(defcustom doc-view-cache-directory
8aafd651 208 (expand-file-name (format "docview%d" (user-uid))
bce6be12 209 temporary-file-directory)
94dbe99c 210 "The base directory, where the PNG images will be saved."
c9a9a5e3 211 :type 'directory
94dbe99c
TTN
212 :group 'doc-view)
213
56741510
SM
214(defvar doc-view-conversion-buffer " *doc-view conversion output*"
215 "The buffer where messages from the converter programs go to.")
94dbe99c 216
56741510 217(defcustom doc-view-conversion-refresh-interval 1
1f75e53d
GM
218 "Interval in seconds between refreshes of the DocView buffer while converting.
219After such a refresh newly converted pages will be available for
94dbe99c
TTN
220viewing. If set to nil there won't be any refreshes and the
221pages won't be displayed before conversion of the whole document
222has finished."
c9a9a5e3 223 :type 'integer
94dbe99c
TTN
224 :group 'doc-view)
225
0a745733 226(defcustom doc-view-continuous nil
aefcadb6
JL
227 "In Continuous mode reaching the page edge advances to next/previous page.
228When non-nil, scrolling a line upward at the bottom edge of the page
229moves to the next page, and scrolling a line downward at the top edge
230of the page moves to the previous page."
231 :type 'boolean
232 :group 'doc-view
233 :version "23.2")
234
94dbe99c
TTN
235;;;; Internal Variables
236
de171465
SM
237(defun doc-view-new-window-function (winprops)
238 (let ((ol (image-mode-window-get 'overlay winprops)))
0bca393f
SM
239 (when (and ol (not (overlay-buffer ol)))
240 ;; I've seen `ol' be a dead overlay. I do not yet know how this
241 ;; happened, so maybe the bug is elsewhere, but in the mean time,
242 ;; this seems like a safe approach.
243 (setq ol nil))
de171465 244 (if ol
0bca393f
SM
245 (progn
246 (assert (eq (overlay-buffer ol) (current-buffer)))
247 (setq ol (copy-overlay ol)))
10b6e7c1 248 (assert (not (get-char-property (point-min) 'display)))
de171465
SM
249 (setq ol (make-overlay (point-min) (point-max) nil t))
250 (overlay-put ol 'doc-view t))
251 (overlay-put ol 'window (car winprops))
252 (image-mode-window-put 'overlay ol winprops)))
94dbe99c 253
de171465 254(defvar doc-view-current-files nil
94dbe99c 255 "Only used internally.")
ec4853ab 256(make-variable-buffer-local 'doc-view-current-files)
94dbe99c 257
ec4853ab 258(defvar doc-view-current-converter-processes nil
94dbe99c 259 "Only used internally.")
ec4853ab 260(make-variable-buffer-local 'doc-view-current-converter-processes)
94dbe99c
TTN
261
262(defvar doc-view-current-timer nil
263 "Only used internally.")
ec4853ab 264(make-variable-buffer-local 'doc-view-current-timer)
94dbe99c 265
94dbe99c
TTN
266(defvar doc-view-current-cache-dir nil
267 "Only used internally.")
ec4853ab 268(make-variable-buffer-local 'doc-view-current-cache-dir)
94dbe99c
TTN
269
270(defvar doc-view-current-search-matches nil
271 "Only used internally.")
ec4853ab 272(make-variable-buffer-local 'doc-view-current-search-matches)
94dbe99c 273
d99abf1b
RS
274(defvar doc-view-pending-cache-flush nil
275 "Only used internally.")
94dbe99c 276
937cb3fb 277(defvar doc-view-previous-major-mode nil
640602f7
RS
278 "Only used internally.")
279
39a402e3
TH
280(defvar doc-view-buffer-file-name nil
281 "Only used internally.
282The file name used for conversion. Normally it's the same as
283`buffer-file-name', but for remote files, compressed files and
284files inside an archive it is a temporary copy of
285the (uncompressed, extracted) file residing in
286`doc-view-cache-directory'.")
287
eb79098b
SM
288(defvar doc-view-doc-type nil
289 "The type of document in the current buffer.
290Can be `dvi', `pdf', or `ps'.")
291
640602f7 292;;;; DocView Keymaps
94dbe99c
TTN
293
294(defvar doc-view-mode-map
295 (let ((map (make-sparse-keymap)))
d2d7e96c 296 (set-keymap-parent map image-mode-map)
94dbe99c
TTN
297 ;; Navigation in the document
298 (define-key map (kbd "n") 'doc-view-next-page)
299 (define-key map (kbd "p") 'doc-view-previous-page)
eb8d0216
SM
300 (define-key map (kbd "<next>") 'forward-page)
301 (define-key map (kbd "<prior>") 'backward-page)
302 (define-key map [remap forward-page] 'doc-view-next-page)
303 (define-key map [remap backward-page] 'doc-view-previous-page)
94dbe99c
TTN
304 (define-key map (kbd "SPC") 'doc-view-scroll-up-or-next-page)
305 (define-key map (kbd "DEL") 'doc-view-scroll-down-or-previous-page)
aefcadb6
JL
306 (define-key map (kbd "C-n") 'doc-view-next-line-or-next-page)
307 (define-key map (kbd "<down>") 'doc-view-next-line-or-next-page)
308 (define-key map (kbd "C-p") 'doc-view-previous-line-or-previous-page)
309 (define-key map (kbd "<up>") 'doc-view-previous-line-or-previous-page)
94dbe99c
TTN
310 (define-key map (kbd "M-<") 'doc-view-first-page)
311 (define-key map (kbd "M->") 'doc-view-last-page)
7656fe61 312 (define-key map [remap goto-line] 'doc-view-goto-page)
4376876e 313 (define-key map (kbd "RET") 'image-next-line)
05477667
SM
314 ;; Zoom in/out.
315 (define-key map "+" 'doc-view-enlarge)
316 (define-key map "-" 'doc-view-shrink)
d2d7e96c 317 ;; Killing the buffer (and the process)
94dbe99c 318 (define-key map (kbd "k") 'doc-view-kill-proc-and-buffer)
937cb3fb 319 (define-key map (kbd "K") 'doc-view-kill-proc)
94dbe99c
TTN
320 ;; Slicing the image
321 (define-key map (kbd "s s") 'doc-view-set-slice)
322 (define-key map (kbd "s m") 'doc-view-set-slice-using-mouse)
323 (define-key map (kbd "s r") 'doc-view-reset-slice)
324 ;; Searching
325 (define-key map (kbd "C-s") 'doc-view-search)
326 (define-key map (kbd "<find>") 'doc-view-search)
7baca0fa 327 (define-key map (kbd "C-r") 'doc-view-search-backward)
94dbe99c
TTN
328 ;; Show the tooltip
329 (define-key map (kbd "C-t") 'doc-view-show-tooltip)
640602f7
RS
330 ;; Toggle between text and image display or editing
331 (define-key map (kbd "C-c C-c") 'doc-view-toggle-display)
515357c2
TH
332 ;; Open a new buffer with doc's text contents
333 (define-key map (kbd "C-c C-t") 'doc-view-open-text)
0bca393f
SM
334 ;; Reconvert the current document. Don't just use revert-buffer
335 ;; because that resets the scale factor, the page number, ...
336 (define-key map (kbd "g") 'doc-view-revert-buffer)
337 (define-key map (kbd "r") 'doc-view-revert-buffer)
94dbe99c 338 map)
640602f7
RS
339 "Keymap used by `doc-view-mode' when displaying a doc as a set of images.")
340
0bca393f
SM
341(defun doc-view-revert-buffer (&optional ignore-auto noconfirm)
342 "Like `revert-buffer', but preserves the buffer's current modes."
343 ;; FIXME: this should probably be moved to files.el and used for
344 ;; most/all "g" bindings to revert-buffer.
345 (interactive (list (not current-prefix-arg)))
346 (revert-buffer ignore-auto noconfirm 'preserve-modes))
347
348
f4c75497
SM
349(easy-menu-define doc-view-menu doc-view-mode-map
350 "Menu for Doc View mode."
351 '("DocView"
0a745733
JL
352 ["Toggle display" doc-view-toggle-display]
353 ("Continuous"
354 ["Off" (setq doc-view-continuous nil)
355 :style radio :selected (eq doc-view-continuous nil)]
356 ["On" (setq doc-view-continuous t)
357 :style radio :selected (eq doc-view-continuous t)]
358 "---"
359 ["Save as Default"
360 (customize-save-variable 'doc-view-continuous doc-view-continuous) t]
361 )
362 "---"
f4c75497
SM
363 ["Set Slice" doc-view-set-slice-using-mouse]
364 ["Set Slice (manual)" doc-view-set-slice]
365 ["Reset Slice" doc-view-reset-slice]
366 "---"
367 ["Search" doc-view-search]
7baca0fa 368 ["Search Backwards" doc-view-search-backward]
f4c75497
SM
369 ))
370
937cb3fb 371(defvar doc-view-minor-mode-map
640602f7
RS
372 (let ((map (make-sparse-keymap)))
373 ;; Toggle between text and image display or editing
374 (define-key map (kbd "C-c C-c") 'doc-view-toggle-display)
640602f7 375 map)
937cb3fb 376 "Keymap used by `doc-minor-view-mode'.")
94dbe99c
TTN
377
378;;;; Navigation Commands
379
160dfe43
SM
380(defmacro doc-view-current-page (&optional win)
381 `(image-mode-window-get 'page ,win))
de171465
SM
382(defmacro doc-view-current-info () `(image-mode-window-get 'info))
383(defmacro doc-view-current-overlay () `(image-mode-window-get 'overlay))
384(defmacro doc-view-current-image () `(image-mode-window-get 'image))
385(defmacro doc-view-current-slice () `(image-mode-window-get 'slice))
386
bdd42899
SM
387(defun doc-view-last-page-number ()
388 (length doc-view-current-files))
389
94dbe99c
TTN
390(defun doc-view-goto-page (page)
391 "View the page given by PAGE."
392 (interactive "nPage: ")
bdd42899 393 (let ((len (doc-view-last-page-number))
bc19637d 394 (hscroll (window-hscroll)))
94dbe99c 395 (if (< page 1)
1ca678aa 396 (setq page 1)
56741510
SM
397 (when (and (> page len)
398 ;; As long as the converter is running, we don't know
399 ;; how many pages will be available.
ec4853ab 400 (null doc-view-current-converter-processes))
1ca678aa 401 (setq page len)))
de171465
SM
402 (setf (doc-view-current-page) page
403 (doc-view-current-info)
1ca678aa
MC
404 (concat
405 (propertize
de171465 406 (format "Page %d of %d." page len) 'face 'bold)
1ca678aa 407 ;; Tell user if converting isn't finished yet
ec4853ab 408 (if doc-view-current-converter-processes
1ca678aa
MC
409 " (still converting...)\n"
410 "\n")
411 ;; Display context infos if this page matches the last search
412 (when (and doc-view-current-search-matches
de171465 413 (assq page doc-view-current-search-matches))
1ca678aa
MC
414 (concat (propertize "Search matches:\n" 'face 'bold)
415 (let ((contexts ""))
de171465 416 (dolist (m (cdr (assq page
1ca678aa
MC
417 doc-view-current-search-matches)))
418 (setq contexts (concat contexts " - \"" m "\"\n")))
419 contexts)))))
94dbe99c 420 ;; Update the buffer
56741510
SM
421 ;; We used to find the file name from doc-view-current-files but
422 ;; that's not right if the pages are not generated sequentially
423 ;; or if the page isn't in doc-view-current-files yet.
ec4853ab
SM
424 (let ((file (expand-file-name (format "page-%d.png" page)
425 (doc-view-current-cache-dir))))
426 (doc-view-insert-image file :pointer 'arrow)
bc19637d 427 (set-window-hscroll (selected-window) hscroll)
ec4853ab
SM
428 (when (and (not (file-exists-p file))
429 doc-view-current-converter-processes)
430 ;; The PNG file hasn't been generated yet.
431 (doc-view-pdf->png-1 doc-view-buffer-file-name file page
432 (lexical-let ((page page)
bdd42899
SM
433 (win (selected-window))
434 (file file))
ec4853ab
SM
435 (lambda ()
436 (and (eq (current-buffer) (window-buffer win))
437 ;; If we changed page in the mean
438 ;; time, don't mess things up.
439 (eq (doc-view-current-page win) page)
bdd42899
SM
440 ;; Make sure we don't infloop.
441 (file-readable-p file)
ec4853ab
SM
442 (with-selected-window win
443 (doc-view-goto-page page))))))))
de171465
SM
444 (overlay-put (doc-view-current-overlay)
445 'help-echo (doc-view-current-info))))
94dbe99c
TTN
446
447(defun doc-view-next-page (&optional arg)
448 "Browse ARG pages forward."
449 (interactive "p")
de171465 450 (doc-view-goto-page (+ (doc-view-current-page) (or arg 1))))
94dbe99c
TTN
451
452(defun doc-view-previous-page (&optional arg)
453 "Browse ARG pages backward."
454 (interactive "p")
de171465 455 (doc-view-goto-page (- (doc-view-current-page) (or arg 1))))
94dbe99c
TTN
456
457(defun doc-view-first-page ()
458 "View the first page."
459 (interactive)
460 (doc-view-goto-page 1))
461
462(defun doc-view-last-page ()
463 "View the last page."
464 (interactive)
bdd42899 465 (doc-view-goto-page (doc-view-last-page-number)))
94dbe99c 466
7d6b4d3c
JL
467(defun doc-view-scroll-up-or-next-page (&optional arg)
468 "Scroll page up ARG lines if possible, else goto next page.
0a745733 469When `doc-view-continuous' is non-nil, scrolling upward
7d6b4d3c
JL
470at the bottom edge of the page moves to the next page.
471Otherwise, goto next page only on typing SPC (ARG is nil)."
472 (interactive "P")
0a745733 473 (if (or doc-view-continuous (null arg))
7d6b4d3c
JL
474 (let ((hscroll (window-hscroll))
475 (cur-page (doc-view-current-page)))
476 (when (= (window-vscroll) (image-scroll-up arg))
477 (doc-view-next-page)
478 (when (/= cur-page (doc-view-current-page))
479 (image-bob)
480 (image-bol 1))
481 (set-window-hscroll (selected-window) hscroll)))
482 (image-scroll-up arg)))
483
484(defun doc-view-scroll-down-or-previous-page (&optional arg)
485 "Scroll page down ARG lines if possible, else goto previous page.
0a745733 486When `doc-view-continuous' is non-nil, scrolling downward
7d6b4d3c
JL
487at the top edge of the page moves to the previous page.
488Otherwise, goto previous page only on typing DEL (ARG is nil)."
489 (interactive "P")
0a745733 490 (if (or doc-view-continuous (null arg))
7d6b4d3c
JL
491 (let ((hscroll (window-hscroll))
492 (cur-page (doc-view-current-page)))
493 (when (= (window-vscroll) (image-scroll-down arg))
494 (doc-view-previous-page)
495 (when (/= cur-page (doc-view-current-page))
496 (image-eob)
497 (image-bol 1))
498 (set-window-hscroll (selected-window) hscroll)))
499 (image-scroll-down arg)))
500
501(defun doc-view-next-line-or-next-page (&optional arg)
502 "Scroll upward by ARG lines if possible, else goto next page.
0a745733 503When `doc-view-continuous' is non-nil, scrolling a line upward
7d6b4d3c 504at the bottom edge of the page moves to the next page."
aefcadb6 505 (interactive "p")
0a745733 506 (if doc-view-continuous
aefcadb6
JL
507 (let ((hscroll (window-hscroll))
508 (cur-page (doc-view-current-page)))
7d6b4d3c 509 (when (= (window-vscroll) (image-next-line arg))
aefcadb6
JL
510 (doc-view-next-page)
511 (when (/= cur-page (doc-view-current-page))
512 (image-bob)
513 (image-bol 1))
514 (set-window-hscroll (selected-window) hscroll)))
515 (image-next-line 1)))
516
7d6b4d3c
JL
517(defun doc-view-previous-line-or-previous-page (&optional arg)
518 "Scroll downward by ARG lines if possible, else goto previous page.
0a745733 519When `doc-view-continuous' is non-nil, scrolling a line downward
aefcadb6
JL
520at the top edge of the page moves to the previous page."
521 (interactive "p")
0a745733 522 (if doc-view-continuous
aefcadb6
JL
523 (let ((hscroll (window-hscroll))
524 (cur-page (doc-view-current-page)))
7d6b4d3c 525 (when (= (window-vscroll) (image-previous-line arg))
aefcadb6
JL
526 (doc-view-previous-page)
527 (when (/= cur-page (doc-view-current-page))
528 (image-eob)
529 (image-bol 1))
530 (set-window-hscroll (selected-window) hscroll)))
7d6b4d3c 531 (image-previous-line arg)))
aefcadb6 532
937cb3fb
GM
533;;;; Utility Functions
534
640602f7 535(defun doc-view-kill-proc ()
ec4853ab 536 "Kill the current converter process(es)."
640602f7 537 (interactive)
bdd42899 538 (while (consp doc-view-current-converter-processes)
56741510 539 (ignore-errors ;; Maybe it's dead already?
ec4853ab 540 (kill-process (pop doc-view-current-converter-processes))))
640602f7
RS
541 (when doc-view-current-timer
542 (cancel-timer doc-view-current-timer)
543 (setq doc-view-current-timer nil))
544 (setq mode-line-process nil))
545
94dbe99c
TTN
546(defun doc-view-kill-proc-and-buffer ()
547 "Kill the current converter process and buffer."
548 (interactive)
640602f7 549 (doc-view-kill-proc)
94dbe99c 550 (when (eq major-mode 'doc-view-mode)
94dbe99c
TTN
551 (kill-buffer (current-buffer))))
552
c17587fe
SM
553(defun doc-view-make-safe-dir (dir)
554 (condition-case nil
555 (let ((umask (default-file-modes)))
556 (unwind-protect
557 (progn
558 ;; Create temp files with strict access rights. It's easy to
559 ;; loosen them later, whereas it's impossible to close the
560 ;; time-window of loose permissions otherwise.
561 (set-default-file-modes #o0700)
562 (make-directory dir))
563 ;; Reset the umask.
564 (set-default-file-modes umask)))
565 (file-already-exists
566 (if (file-symlink-p dir)
567 (error "Danger: %s points to a symbolic link" dir))
568 ;; In case it was created earlier with looser rights.
569 ;; We could check the mode info returned by file-attributes, but it's
570 ;; a pain to parse and it may not tell you what we want under
571 ;; non-standard file-systems. So let's just say what we want and let
572 ;; the underlying C code and file-system figure it out.
573 ;; This also ends up checking a bunch of useful conditions: it makes
574 ;; sure we have write-access to the directory and that we own it, thus
575 ;; closing a bunch of security holes.
576 (set-file-modes dir #o0700))))
577
937cb3fb
GM
578(defun doc-view-current-cache-dir ()
579 "Return the directory where the png files of the current doc should be saved.
580It's a subdirectory of `doc-view-cache-directory'."
581 (if doc-view-current-cache-dir
582 doc-view-current-cache-dir
c17587fe
SM
583 ;; Try and make sure doc-view-cache-directory exists and is safe.
584 (doc-view-make-safe-dir doc-view-cache-directory)
585 ;; Now compute the subdirectory to use.
937cb3fb
GM
586 (setq doc-view-current-cache-dir
587 (file-name-as-directory
c17587fe 588 (expand-file-name
5e9fde5e 589 (concat (file-name-nondirectory doc-view-buffer-file-name)
39a402e3
TH
590 "-"
591 (let ((file doc-view-buffer-file-name))
592 (with-temp-buffer
86903c81 593 (set-buffer-multibyte nil)
39a402e3
TH
594 (insert-file-contents-literally file)
595 (md5 (current-buffer)))))
c17587fe 596 doc-view-cache-directory)))))
937cb3fb
GM
597
598(defun doc-view-remove-if (predicate list)
599 "Return LIST with all items removed that satisfy PREDICATE."
600 (let (new-list)
601 (dolist (item list (nreverse new-list))
602 (when (not (funcall predicate item))
603 (setq new-list (cons item new-list))))))
604
789ab9d4
RS
605;;;###autoload
606(defun doc-view-mode-p (type)
607 "Return non-nil if image type TYPE is available for `doc-view'.
608Image types are symbols like `dvi', `postscript' or `pdf'."
609 (and (display-graphic-p)
610 (image-type-available-p 'png)
611 (cond
612 ((eq type 'dvi)
613 (and (doc-view-mode-p 'pdf)
53d4c024
TH
614 (or (and doc-view-dvipdf-program
615 (executable-find doc-view-dvipdf-program))
616 (and doc-view-dvipdfm-program
617 (executable-find doc-view-dvipdfm-program)))))
622face2 618 ((or (eq type 'postscript) (eq type 'ps) (eq type 'eps)
789ab9d4
RS
619 (eq type 'pdf))
620 (and doc-view-ghostscript-program
621 (executable-find doc-view-ghostscript-program)))
622 (t ;; unknown image type
623 nil))))
624
94dbe99c
TTN
625;;;; Conversion Functions
626
05477667
SM
627(defvar doc-view-shrink-factor 1.125)
628
629(defun doc-view-enlarge (factor)
630 "Enlarge the document."
631 (interactive (list doc-view-shrink-factor))
632 (set (make-local-variable 'doc-view-resolution)
633 (* factor doc-view-resolution))
634 (doc-view-reconvert-doc))
635
636(defun doc-view-shrink (factor)
637 "Shrink the document."
638 (interactive (list doc-view-shrink-factor))
639 (doc-view-enlarge (/ 1.0 factor)))
640
c17587fe 641(defun doc-view-reconvert-doc ()
640602f7
RS
642 "Reconvert the current document.
643Should be invoked when the cached images aren't up-to-date."
644 (interactive)
f4c75497
SM
645 (doc-view-kill-proc)
646 ;; Clear the old cached files
647 (when (file-exists-p (doc-view-current-cache-dir))
bdd42899 648 (delete-directory (doc-view-current-cache-dir) 'recursive))
c17587fe 649 (doc-view-initiate-display))
94dbe99c 650
b4cb319f
SM
651(defun doc-view-sentinel (proc event)
652 "Generic sentinel for doc-view conversion processes."
94dbe99c 653 (if (not (string-match "finished" event))
b4cb319f 654 (message "DocView: process %s changed status to %s."
405ee48d
CY
655 (process-name proc)
656 (if (string-match "\\(.+\\)\n?\\'" event)
657 (match-string 1 event)
658 event))
ec4853ab
SM
659 (when (buffer-live-p (process-get proc 'buffer))
660 (with-current-buffer (process-get proc 'buffer)
661 (setq doc-view-current-converter-processes
662 (delq proc doc-view-current-converter-processes))
663 (setq mode-line-process
664 (if doc-view-current-converter-processes
665 (format ":%s" (car doc-view-current-converter-processes))))
666 (funcall (process-get proc 'callback))))))
667
668(defun doc-view-start-process (name program args callback)
3c03f2ce
TH
669 ;; Make sure the process is started in an existing directory, (rather than
670 ;; some file-name-handler-managed dir, for example).
671 (let* ((default-directory (if (file-readable-p default-directory)
672 default-directory
673 (expand-file-name "~/")))
ec4853ab
SM
674 (proc (apply 'start-process name doc-view-conversion-buffer
675 program args)))
676 (push proc doc-view-current-converter-processes)
677 (setq mode-line-process (list (format ":%s" proc)))
678 (set-process-sentinel proc 'doc-view-sentinel)
679 (process-put proc 'buffer (current-buffer))
680 (process-put proc 'callback callback)))
b4cb319f
SM
681
682(defun doc-view-dvi->pdf (dvi pdf callback)
683 "Convert DVI to PDF asynchronously and call CALLBACK when finished."
53d4c024
TH
684 ;; Prefer dvipdf over dvipdfm, because the latter has problems if the DVI
685 ;; references and includes other PS files.
686 (if (and doc-view-dvipdf-program
687 (executable-find doc-view-dvipdf-program))
688 (doc-view-start-process "dvi->pdf" doc-view-dvipdf-program
689 (list dvi pdf)
690 callback)
691 (doc-view-start-process "dvi->pdf" doc-view-dvipdfm-program
692 (list "-o" pdf dvi)
693 callback)))
ec4853ab 694
94dbe99c
TTN
695
696(defun doc-view-pdf/ps->png (pdf-ps png)
1f75e53d 697 "Convert PDF-PS to PNG asynchronously."
ec4853ab
SM
698 (doc-view-start-process
699 "pdf/ps->png" doc-view-ghostscript-program
700 (append doc-view-ghostscript-options
701 (list (format "-r%d" (round doc-view-resolution))
702 (concat "-sOutputFile=" png)
703 pdf-ps))
bdd42899
SM
704 (lexical-let ((resolution doc-view-resolution))
705 (lambda ()
706 ;; Only create the resolution file when it's all done, so it also
707 ;; serves as a witness that the conversion is complete.
708 (write-region (prin1-to-string resolution) nil
709 (expand-file-name "resolution.el"
710 (doc-view-current-cache-dir))
711 nil 'silently)
712 (when doc-view-current-timer
713 (cancel-timer doc-view-current-timer)
714 (setq doc-view-current-timer nil))
715 (doc-view-display (current-buffer) 'force))))
ec4853ab 716 ;; Update the displayed pages as soon as they're done generating.
94dbe99c
TTN
717 (when doc-view-conversion-refresh-interval
718 (setq doc-view-current-timer
ec4853ab
SM
719 (run-at-time "1 secs" doc-view-conversion-refresh-interval
720 'doc-view-display
721 (current-buffer)))))
722
723(defun doc-view-pdf->png-1 (pdf png page callback)
724 "Convert a PAGE of a PDF file to PNG asynchronously.
725Call CALLBACK with no arguments when done."
726 (doc-view-start-process
727 "pdf->png-1" doc-view-ghostscript-program
728 (append doc-view-ghostscript-options
729 (list (format "-r%d" (round doc-view-resolution))
730 ;; Sadly, `gs' only supports the page-range
731 ;; for PDF files.
732 (format "-dFirstPage=%d" page)
733 (format "-dLastPage=%d" page)
734 (concat "-sOutputFile=" png)
735 pdf))
736 callback))
737
aa360da1
GM
738(declare-function clear-image-cache "image.c" (&optional filter))
739
ec4853ab
SM
740(defun doc-view-pdf->png (pdf png pages)
741 "Convert a PDF file to PNG asynchronously.
742Start by converting PAGES, and then the rest."
743 (if (null pages)
744 (doc-view-pdf/ps->png pdf png)
745 ;; We could render several `pages' with a single process if they're
746 ;; (almost) consecutive, but since in 99% of the cases, there'll be only
747 ;; a single page anyway, and of the remaining 1%, few cases will have
748 ;; consecutive pages, it's not worth the trouble.
749 (lexical-let ((pdf pdf) (png png) (rest (cdr pages)))
750 (doc-view-pdf->png-1
751 pdf (format png (car pages)) (car pages)
752 (lambda ()
753 (if rest
754 (doc-view-pdf->png pdf png rest)
755 ;; Yippie, the important pages are done, update the display.
756 (clear-image-cache)
bdd42899
SM
757 ;; For the windows that have a message (like "Welcome to
758 ;; DocView") display property, clearing the image cache is
759 ;; not sufficient.
760 (dolist (win (get-buffer-window-list (current-buffer) nil 'visible))
761 (with-selected-window win
762 (when (stringp (get-char-property (point-min) 'display))
763 (doc-view-goto-page (doc-view-current-page)))))
ec4853ab
SM
764 ;; Convert the rest of the pages.
765 (doc-view-pdf/ps->png pdf png)))))))
94dbe99c 766
b4cb319f
SM
767(defun doc-view-pdf->txt (pdf txt callback)
768 "Convert PDF to TXT asynchronously and call CALLBACK when finished."
ca32d854
GM
769 (or doc-view-pdftotext-program
770 (error "You need the `pdftotext' program to convert a PDF to text"))
ec4853ab
SM
771 (doc-view-start-process "pdf->txt" doc-view-pdftotext-program
772 (list "-raw" pdf txt)
773 callback))
515357c2 774
b4cb319f
SM
775(defun doc-view-doc->txt (txt callback)
776 "Convert the current document to text and call CALLBACK when done."
7edd6b92 777 (make-directory (doc-view-current-cache-dir) t)
b4cb319f
SM
778 (case doc-view-doc-type
779 (pdf
780 ;; Doc is a PDF, so convert it to TXT
781 (doc-view-pdf->txt doc-view-buffer-file-name txt callback))
782 (ps
783 ;; Doc is a PS, so convert it to PDF (which will be converted to
784 ;; TXT thereafter).
785 (lexical-let ((pdf (expand-file-name "doc.pdf"
786 (doc-view-current-cache-dir)))
787 (txt txt)
788 (callback callback))
789 (doc-view-ps->pdf doc-view-buffer-file-name pdf
790 (lambda () (doc-view-pdf->txt pdf txt callback)))))
791 (dvi
792 ;; Doc is a DVI. This means that a doc.pdf already exists in its
793 ;; cache subdirectory.
794 (doc-view-pdf->txt (expand-file-name "doc.pdf"
795 (doc-view-current-cache-dir))
796 txt callback))
797 (t (error "DocView doesn't know what to do"))))
798
799(defun doc-view-ps->pdf (ps pdf callback)
800 "Convert PS to PDF asynchronously and call CALLBACK when finished."
ca32d854
GM
801 (or doc-view-ps2pdf-program
802 (error "You need the `ps2pdf' program to convert PS to PDF"))
ec4853ab
SM
803 (doc-view-start-process "ps->pdf" doc-view-ps2pdf-program
804 (list
805 ;; Avoid security problems when rendering files from
806 ;; untrusted sources.
807 "-dSAFER"
808 ;; in-file and out-file
809 ps pdf)
810 callback))
811
812(defun doc-view-active-pages ()
813 (let ((pages ()))
814 (dolist (win (get-buffer-window-list (current-buffer) nil 'visible))
815 (let ((page (image-mode-window-get 'page win)))
816 (unless (memq page pages) (push page pages))))
817 pages))
94dbe99c 818
640602f7 819(defun doc-view-convert-current-doc ()
39a402e3 820 "Convert `doc-view-buffer-file-name' to a set of png files, one file per page.
640602f7
RS
821Those files are saved in the directory given by the function
822`doc-view-current-cache-dir'."
c17587fe
SM
823 ;; Let stale files still display while we recompute the new ones, so only
824 ;; flush the cache when the conversion is over. One of the reasons why it
825 ;; is important to keep displaying the stale page is so that revert-buffer
826 ;; preserves the horizontal/vertical scroll settings (which are otherwise
827 ;; resets during the redisplay).
828 (setq doc-view-pending-cache-flush t)
829 (let ((png-file (expand-file-name "page-%d.png"
830 (doc-view-current-cache-dir))))
7edd6b92 831 (make-directory (doc-view-current-cache-dir) t)
eb79098b
SM
832 (case doc-view-doc-type
833 (dvi
834 ;; DVI files have to be converted to PDF before Ghostscript can process
835 ;; it.
b4cb319f
SM
836 (lexical-let
837 ((pdf (expand-file-name "doc.pdf" doc-view-current-cache-dir))
838 (png-file png-file))
839 (doc-view-dvi->pdf doc-view-buffer-file-name pdf
840 (lambda () (doc-view-pdf/ps->png pdf png-file)))))
bdd42899
SM
841 (pdf
842 (let ((pages (doc-view-active-pages)))
843 ;; Convert PDF to PNG images starting with the active pages.
844 (doc-view-pdf->png doc-view-buffer-file-name png-file pages)))
eb79098b
SM
845 (t
846 ;; Convert to PNG images.
847 (doc-view-pdf/ps->png doc-view-buffer-file-name png-file)))))
94dbe99c 848
94dbe99c
TTN
849;;;; Slicing
850
aa360da1
GM
851(declare-function image-size "image.c" (spec &optional pixels frame))
852
94dbe99c
TTN
853(defun doc-view-set-slice (x y width height)
854 "Set the slice of the images that should be displayed.
855You can use this function to tell doc-view not to display the
856margins of the document. It prompts for the top-left corner (X
857and Y) of the slice to display and its WIDTH and HEIGHT.
858
859See `doc-view-set-slice-using-mouse' for a more convenient way to
860do that. To reset the slice use `doc-view-reset-slice'."
861 (interactive
de171465 862 (let* ((size (image-size (doc-view-current-image) t))
1ca678aa
MC
863 (a (read-number (format "Top-left X (0..%d): " (car size))))
864 (b (read-number (format "Top-left Y (0..%d): " (cdr size))))
865 (c (read-number (format "Width (0..%d): " (- (car size) a))))
866 (d (read-number (format "Height (0..%d): " (- (cdr size) b)))))
94dbe99c 867 (list a b c d)))
de171465 868 (setf (doc-view-current-slice) (list x y width height))
94dbe99c 869 ;; Redisplay
de171465 870 (doc-view-goto-page (doc-view-current-page)))
94dbe99c
TTN
871
872(defun doc-view-set-slice-using-mouse ()
873 "Set the slice of the images that should be displayed.
874You set the slice by pressing mouse-1 at its top-left corner and
875dragging it to its bottom-right corner. See also
876`doc-view-set-slice' and `doc-view-reset-slice'."
877 (interactive)
878 (let (x y w h done)
879 (while (not done)
880 (let ((e (read-event
1ca678aa
MC
881 (concat "Press mouse-1 at the top-left corner and "
882 "drag it to the bottom-right corner!"))))
883 (when (eq (car e) 'drag-mouse-1)
884 (setq x (car (posn-object-x-y (event-start e))))
885 (setq y (cdr (posn-object-x-y (event-start e))))
886 (setq w (- (car (posn-object-x-y (event-end e))) x))
887 (setq h (- (cdr (posn-object-x-y (event-end e))) y))
888 (setq done t))))
94dbe99c
TTN
889 (doc-view-set-slice x y w h)))
890
891(defun doc-view-reset-slice ()
e48a5bf9 892 "Reset the current slice.
1f75e53d 893After calling this function whole pages will be visible again."
94dbe99c 894 (interactive)
de171465 895 (setf (doc-view-current-slice) nil)
94dbe99c 896 ;; Redisplay
de171465 897 (doc-view-goto-page (doc-view-current-page)))
94dbe99c
TTN
898
899;;;; Display
900
901(defun doc-view-insert-image (file &rest args)
902 "Insert the given png FILE.
e48a5bf9 903ARGS is a list of image descriptors."
c17587fe
SM
904 (when doc-view-pending-cache-flush
905 (clear-image-cache)
906 (setq doc-view-pending-cache-flush nil))
de171465 907 (let ((ol (doc-view-current-overlay))
56741510
SM
908 (image (if (and file (file-readable-p file))
909 (apply 'create-image file 'png nil args)))
de171465
SM
910 (slice (doc-view-current-slice)))
911 (setf (doc-view-current-image) image)
56741510 912 (move-overlay ol (point-min) (point-max))
de171465 913 (overlay-put ol 'display
56741510
SM
914 (cond
915 (image
de171465
SM
916 (if slice
917 (list (cons 'slice slice) image)
56741510
SM
918 image))
919 ;; We're trying to display a page that doesn't exist.
ec4853ab 920 (doc-view-current-converter-processes
56741510
SM
921 ;; Maybe the page doesn't exist *yet*.
922 "Cannot display this page (yet)!")
923 (t
924 ;; Typically happens if the conversion process somehow
925 ;; failed. Better not signal an error here because it
926 ;; could prevent a subsequent reconversion from fixing
927 ;; the problem.
928 (concat "Cannot display this page!\n"
929 "Maybe because of a conversion failure!"))))
930 (let ((win (overlay-get ol 'window)))
931 (if (stringp (overlay-get ol 'display))
932 (progn ;Make sure the text is not scrolled out of view.
933 (set-window-hscroll win 0)
934 (set-window-vscroll win 0))
935 (let ((hscroll (image-mode-window-get 'hscroll win))
936 (vscroll (image-mode-window-get 'vscroll win)))
937 ;; Reset scroll settings, in case they were changed.
938 (if hscroll (set-window-hscroll win hscroll))
939 (if vscroll (set-window-vscroll win vscroll)))))))
94dbe99c
TTN
940
941(defun doc-view-sort (a b)
942 "Return non-nil if A should be sorted before B.
943Predicate for sorting `doc-view-current-files'."
f4c75497
SM
944 (or (< (length a) (length b))
945 (and (= (length a) (length b))
946 (string< a b))))
94dbe99c 947
65073003
SM
948(defun doc-view-display (buffer &optional force)
949 "Start viewing the document in BUFFER.
214abdd4
SM
950If FORCE is non-nil, start viewing even if the document does not
951have the page we want to view."
65073003 952 (with-current-buffer buffer
56741510
SM
953 (let ((prev-pages doc-view-current-files))
954 (setq doc-view-current-files
955 (sort (directory-files (doc-view-current-cache-dir) t
956 "page-[0-9]+\\.png" t)
957 'doc-view-sort))
10b6e7c1
CY
958 (dolist (win (or (get-buffer-window-list buffer nil t)
959 (list (selected-window))))
960 (let* ((page (doc-view-current-page win))
961 (pagefile (expand-file-name (format "page-%d.png" page)
962 (doc-view-current-cache-dir))))
963 (when (or force
964 (and (not (member pagefile prev-pages))
965 (member pagefile doc-view-current-files)))
966 (with-selected-window win
967 (assert (eq (current-buffer) buffer))
968 (doc-view-goto-page page))))))))
94dbe99c
TTN
969
970(defun doc-view-buffer-message ()
c17587fe
SM
971 ;; Only show this message initially, not when refreshing the buffer (in which
972 ;; case it's better to keep displaying the "stale" page while computing
973 ;; the fresh new ones).
de171465
SM
974 (unless (overlay-get (doc-view-current-overlay) 'display)
975 (overlay-put (doc-view-current-overlay) 'display
c17587fe
SM
976 (concat (propertize "Welcome to DocView!" 'face 'bold)
977 "\n"
978 "
1f75e53d 979If you see this buffer it means that the document you want to view is being
f4c75497 980converted to PNG and the conversion of the first page hasn't finished yet or
94dbe99c
TTN
981`doc-view-conversion-refresh-interval' is set to nil.
982
983For now these keys are useful:
984
1ca678aa 985`q' : Bury this buffer. Conversion will go on in background.
937cb3fb 986`k' : Kill the conversion process and this buffer.
c17587fe 987`K' : Kill the conversion process.\n"))))
94dbe99c 988
aa360da1
GM
989(declare-function tooltip-show "tooltip" (text &optional use-echo-area))
990
94dbe99c
TTN
991(defun doc-view-show-tooltip ()
992 (interactive)
de171465 993 (tooltip-show (doc-view-current-info)))
94dbe99c 994
515357c2
TH
995(defun doc-view-open-text ()
996 "Open a buffer with the current doc's contents as text."
997 (interactive)
ec4853ab 998 (if doc-view-current-converter-processes
515357c2
TH
999 (message "DocView: please wait till conversion finished.")
1000 (let ((txt (expand-file-name "doc.txt" (doc-view-current-cache-dir))))
1001 (if (file-readable-p txt)
1002 (find-file txt)
b4cb319f 1003 (doc-view-doc->txt txt 'doc-view-open-text)))))
515357c2 1004
937cb3fb 1005;;;;; Toggle between editing and viewing
640602f7 1006
515357c2 1007
640602f7 1008(defun doc-view-toggle-display ()
937cb3fb 1009 "Toggle between editing a document as text or viewing it."
640602f7 1010 (interactive)
937cb3fb
GM
1011 (if (eq major-mode 'doc-view-mode)
1012 ;; Switch to editing mode
1013 (progn
1014 (doc-view-kill-proc)
1015 (setq buffer-read-only nil)
515357c2
TH
1016 (remove-overlays (point-min) (point-max) 'doc-view t)
1017 (set (make-local-variable 'image-mode-winprops-alist) t)
937cb3fb
GM
1018 ;; Switch to the previously used major mode or fall back to fundamental
1019 ;; mode.
1020 (if doc-view-previous-major-mode
1021 (funcall doc-view-previous-major-mode)
1022 (fundamental-mode))
c17587fe 1023 (doc-view-minor-mode 1))
937cb3fb
GM
1024 ;; Switch to doc-view-mode
1025 (when (and (buffer-modified-p)
1026 (y-or-n-p "The buffer has been modified. Save the changes? "))
1027 (save-buffer))
937cb3fb 1028 (doc-view-mode)))
640602f7 1029
94dbe99c
TTN
1030;;;; Searching
1031
515357c2 1032
94dbe99c
TTN
1033(defun doc-view-search-internal (regexp file)
1034 "Return a list of FILE's pages that contain text matching REGEXP.
1ca678aa
MC
1035The value is an alist of the form (PAGE CONTEXTS) where PAGE is
1036the pagenumber and CONTEXTS are all lines of text containing a match."
94dbe99c
TTN
1037 (with-temp-buffer
1038 (insert-file-contents file)
1039 (let ((page 1)
1ca678aa
MC
1040 (lastpage 1)
1041 matches)
94dbe99c 1042 (while (re-search-forward (concat "\\(?:\\([\f]\\)\\|\\("
1ca678aa 1043 regexp "\\)\\)") nil t)
069b4ce3 1044 (when (match-string 1) (setq page (1+ page)))
1ca678aa
MC
1045 (when (match-string 2)
1046 (if (/= page lastpage)
c17587fe 1047 (push (cons page
515357c2
TH
1048 (list (buffer-substring
1049 (line-beginning-position)
1050 (line-end-position))))
1051 matches)
1ca678aa
MC
1052 (setq matches (cons
1053 (append
1054 (or
1055 ;; This page already is a match.
1056 (car matches)
1057 ;; This is the first match on page.
1058 (list page))
1059 (list (buffer-substring
1060 (line-beginning-position)
1061 (line-end-position))))
1062 (cdr matches))))
1063 (setq lastpage page)))
94dbe99c
TTN
1064 (nreverse matches))))
1065
1066(defun doc-view-search-no-of-matches (list)
1067 "Extract the number of matches from the search result LIST."
1068 (let ((no 0))
1069 (dolist (p list)
1070 (setq no (+ no (1- (length p)))))
1071 no))
1072
7baca0fa
JL
1073(defun doc-view-search-backward (new-query)
1074 "Call `doc-view-search' for backward search.
1075If prefix NEW-QUERY is given, ask for a new regexp."
1076 (interactive "P")
da99b369 1077 (doc-view-search new-query t))
7baca0fa
JL
1078
1079(defun doc-view-search (new-query &optional backward)
1080 "Jump to the next match or initiate a new search if NEW-QUERY is given.
94dbe99c 1081If the current document hasn't been transformed to plain text
7baca0fa
JL
1082till now do that first.
1083If BACKWARD is non-nil, jump to the previous match."
1084 (interactive "P")
da99b369 1085 (if (and (not new-query)
7baca0fa
JL
1086 doc-view-current-search-matches)
1087 (if backward
1088 (doc-view-search-previous-match 1)
1089 (doc-view-search-next-match 1))
1090 ;; New search, so forget the old results.
1091 (setq doc-view-current-search-matches nil)
1092 (let ((txt (expand-file-name "doc.txt"
1093 (doc-view-current-cache-dir))))
1094 (if (file-readable-p txt)
1095 (progn
1096 (setq doc-view-current-search-matches
1097 (doc-view-search-internal
1098 (read-from-minibuffer "Regexp: ")
1099 txt))
1100 (message "DocView: search yielded %d matches."
1101 (doc-view-search-no-of-matches
1102 doc-view-current-search-matches)))
1103 ;; We must convert to TXT first!
ec4853ab 1104 (if doc-view-current-converter-processes
7baca0fa 1105 (message "DocView: please wait till conversion finished.")
b4cb319f 1106 (doc-view-doc->txt txt (lambda () (doc-view-search nil))))))))
94dbe99c
TTN
1107
1108(defun doc-view-search-next-match (arg)
1109 "Go to the ARGth next matching page."
1110 (interactive "p")
937cb3fb 1111 (let* ((next-pages (doc-view-remove-if
de171465 1112 (lambda (i) (<= (car i) (doc-view-current-page)))
937cb3fb 1113 doc-view-current-search-matches))
1ca678aa 1114 (page (car (nth (1- arg) next-pages))))
94dbe99c 1115 (if page
1ca678aa 1116 (doc-view-goto-page page)
94dbe99c 1117 (when (and
1ca678aa
MC
1118 doc-view-current-search-matches
1119 (y-or-n-p "No more matches after current page. Wrap to first match? "))
1120 (doc-view-goto-page (caar doc-view-current-search-matches))))))
94dbe99c
TTN
1121
1122(defun doc-view-search-previous-match (arg)
1123 "Go to the ARGth previous matching page."
1124 (interactive "p")
937cb3fb 1125 (let* ((prev-pages (doc-view-remove-if
de171465 1126 (lambda (i) (>= (car i) (doc-view-current-page)))
937cb3fb 1127 doc-view-current-search-matches))
1ca678aa 1128 (page (car (nth (1- arg) (nreverse prev-pages)))))
94dbe99c 1129 (if page
1ca678aa 1130 (doc-view-goto-page page)
94dbe99c 1131 (when (and
1ca678aa
MC
1132 doc-view-current-search-matches
1133 (y-or-n-p "No more matches before current page. Wrap to last match? "))
1134 (doc-view-goto-page (caar (last doc-view-current-search-matches)))))))
94dbe99c 1135
640602f7 1136;;;; User interface commands and the mode
94dbe99c 1137
0d17c4b9 1138(put 'doc-view-mode 'mode-class 'special)
c17587fe 1139
515357c2
TH
1140(defun doc-view-already-converted-p ()
1141 "Return non-nil if the current doc was already converted."
1142 (and (file-exists-p (doc-view-current-cache-dir))
bdd42899
SM
1143 ;; Check that the resolution info is there, otherwise it means
1144 ;; the conversion is incomplete.
1145 (file-readable-p (expand-file-name "resolution.el"
1146 (doc-view-current-cache-dir)))
1147 (> (length (directory-files (doc-view-current-cache-dir)
1148 nil "\\.png\\'"))
1149 0)))
515357c2 1150
c17587fe
SM
1151(defun doc-view-initiate-display ()
1152 ;; Switch to image display if possible
322f4559 1153 (if (doc-view-mode-p doc-view-doc-type)
c17587fe
SM
1154 (progn
1155 (doc-view-buffer-message)
de171465 1156 (setf (doc-view-current-page) (or (doc-view-current-page) 1))
515357c2 1157 (if (doc-view-already-converted-p)
c17587fe
SM
1158 (progn
1159 (message "DocView: using cached files!")
cde4c3f1 1160 ;; Load the saved resolution
bdd42899
SM
1161 (let* ((res-file (expand-file-name "resolution.el"
1162 (doc-view-current-cache-dir)))
1163 (res
1164 (with-temp-buffer
1165 (when (file-readable-p res-file)
1166 (insert-file-contents res-file)
1167 (read (current-buffer))))))
1168 (when (numberp res)
cde4c3f1 1169 (set (make-local-variable 'doc-view-resolution) res)))
65073003 1170 (doc-view-display (current-buffer) 'force))
c17587fe
SM
1171 (doc-view-convert-current-doc))
1172 (message
1173 "%s"
1174 (substitute-command-keys
1175 (concat "Type \\[doc-view-toggle-display] to toggle between "
1176 "editing or viewing the document."))))
1177 (message
1178 "%s"
0855c2ca
CY
1179 (concat "No PNG support is available, or some conversion utility for "
1180 (file-name-extension doc-view-buffer-file-name)
1181 " files is missing."))
1182 (if (and (executable-find doc-view-pdftotext-program)
1183 (y-or-n-p
1184 "Unable to render file. View extracted text instead? "))
1185 (doc-view-open-text)
1186 (doc-view-toggle-display))))
94dbe99c 1187
e0385bf4 1188(defvar bookmark-make-record-function)
069b4ce3 1189
65073003
SM
1190(defun doc-view-clone-buffer-hook ()
1191 ;; FIXME: There are several potential problems linked with reconversion
1192 ;; and auto-revert when we have indirect buffers because they share their
1193 ;; /tmp cache directory. This sharing is good (you'd rather not reconvert
1194 ;; for each clone), but that means that clones need to collaborate a bit.
1195 ;; I guess it mostly means: detect when a reconversion process is already
1196 ;; running, and run the sentinel in all clones.
515357c2 1197 ;;
de171465 1198 ;; Maybe the clones should really have a separate /tmp directory
65073003
SM
1199 ;; so they could have a different resolution and you could use clones
1200 ;; for zooming.
de171465
SM
1201 (remove-overlays (point-min) (point-max) 'doc-view t)
1202 (if (consp image-mode-winprops-alist) (setq image-mode-winprops-alist nil)))
65073003 1203
eb79098b
SM
1204(defun doc-view-intersection (l1 l2)
1205 (let ((l ()))
1206 (dolist (x l1) (if (memq x l2) (push x l)))
1207 l))
1208
640602f7 1209;;;###autoload
937cb3fb 1210(defun doc-view-mode ()
640602f7 1211 "Major mode in DocView buffers.
06a21f70
TH
1212
1213DocView Mode is an Emacs document viewer. It displays PDF, PS
1214and DVI files (as PNG images) in Emacs buffers.
1215
640602f7 1216You can use \\<doc-view-mode-map>\\[doc-view-toggle-display] to
5c1f16b0
SM
1217toggle between displaying the document or editing it as text.
1218\\{doc-view-mode-map}"
937cb3fb 1219 (interactive)
f9adf05b 1220
96fe38a8 1221 (if (= (point-min) (point-max))
85699772 1222 ;; The doc is empty or doesn't exist at all, so fallback to
96fe38a8
SM
1223 ;; another mode. We used to also check file-exists-p, but this
1224 ;; returns nil for tar members.
85699772
TH
1225 (let ((auto-mode-alist (remq (rassq 'doc-view-mode auto-mode-alist)
1226 auto-mode-alist)))
1227 (normal-mode))
06a21f70
TH
1228
1229 (let* ((prev-major-mode (if (eq major-mode 'doc-view-mode)
1230 doc-view-previous-major-mode
1231 major-mode)))
1232 (kill-all-local-variables)
1233 (set (make-local-variable 'doc-view-previous-major-mode) prev-major-mode))
bc19637d 1234
06a21f70
TH
1235 ;; Figure out the document type.
1236 (let ((name-types
1237 (when buffer-file-name
1238 (cdr (assoc (file-name-extension buffer-file-name)
1239 '(("dvi" dvi)
1240 ("pdf" pdf)
1241 ("epdf" pdf)
1242 ("ps" ps)
1243 ("eps" ps))))))
1244 (content-types
1245 (save-excursion
1246 (goto-char (point-min))
1247 (cond
1248 ((looking-at "%!") '(ps))
1249 ((looking-at "%PDF") '(pdf))
1250 ((looking-at "\367\002") '(dvi))))))
1251 (set (make-local-variable 'doc-view-doc-type)
1252 (car (or (doc-view-intersection name-types content-types)
1253 (when (and name-types content-types)
1254 (error "Conflicting types: name says %s but content says %s"
1255 name-types content-types))
1256 name-types content-types
1257 (error "Cannot determine the document type")))))
bc19637d 1258
06a21f70
TH
1259 (doc-view-make-safe-dir doc-view-cache-directory)
1260 ;; Handle compressed files, remote files, files inside archives
1261 (set (make-local-variable 'doc-view-buffer-file-name)
1262 (cond
1263 (jka-compr-really-do-compress
5e9fde5e 1264 ;; FIXME: there's a risk of name conflicts here.
06a21f70
TH
1265 (expand-file-name
1266 (file-name-nondirectory
1267 (file-name-sans-extension buffer-file-name))
1268 doc-view-cache-directory))
1269 ;; Is the file readable by local processes?
1270 ;; We used to use `file-remote-p' but it's unclear what it's
1271 ;; supposed to return nil for things like local files accessed via
1272 ;; `su' or via file://...
1273 ((let ((file-name-handler-alist nil))
5e9fde5e
SM
1274 (not (and buffer-file-name (file-readable-p buffer-file-name))))
1275 ;; FIXME: there's a risk of name conflicts here.
06a21f70 1276 (expand-file-name
5e9fde5e
SM
1277 (if buffer-file-name
1278 (file-name-nondirectory buffer-file-name)
1279 (buffer-name))
1280 doc-view-cache-directory))
06a21f70
TH
1281 (t buffer-file-name)))
1282 (when (not (string= doc-view-buffer-file-name buffer-file-name))
1283 (write-region nil nil doc-view-buffer-file-name))
bc19637d 1284
06a21f70
TH
1285 (add-hook 'change-major-mode-hook
1286 (lambda ()
1287 (doc-view-kill-proc)
1288 (remove-overlays (point-min) (point-max) 'doc-view t))
1289 nil t)
1290 (add-hook 'clone-indirect-buffer-hook 'doc-view-clone-buffer-hook nil t)
1291 (add-hook 'kill-buffer-hook 'doc-view-kill-proc nil t)
bc19637d 1292
06a21f70
TH
1293 (remove-overlays (point-min) (point-max) 'doc-view t) ;Just in case.
1294 ;; Keep track of display info ([vh]scroll, page number, overlay,
1295 ;; ...) for each window in which this document is shown.
1296 (add-hook 'image-mode-new-window-functions
1297 'doc-view-new-window-function nil t)
1298 (image-mode-setup-winprops)
bc19637d 1299
06a21f70
TH
1300 (set (make-local-variable 'mode-line-position)
1301 '(" P" (:eval (number-to-string (doc-view-current-page)))
bdd42899 1302 "/" (:eval (number-to-string (doc-view-last-page-number)))))
06a21f70
TH
1303 ;; Don't scroll unless the user specifically asked for it.
1304 (set (make-local-variable 'auto-hscroll-mode) nil)
7d6b4d3c
JL
1305 (set (make-local-variable 'mwheel-scroll-up-function)
1306 'doc-view-scroll-up-or-next-page)
1307 (set (make-local-variable 'mwheel-scroll-down-function)
1308 'doc-view-scroll-down-or-previous-page)
06a21f70
TH
1309 (set (make-local-variable 'cursor-type) nil)
1310 (use-local-map doc-view-mode-map)
1311 (set (make-local-variable 'after-revert-hook) 'doc-view-reconvert-doc)
1312 (set (make-local-variable 'bookmark-make-record-function)
1313 'doc-view-bookmark-make-record)
1314 (setq mode-name "DocView"
1315 buffer-read-only t
1316 major-mode 'doc-view-mode)
1317 (doc-view-initiate-display)
d1d33062
TH
1318 ;; Switch off view-mode explicitly, because doc-view-mode is the
1319 ;; canonical view mode for PDF/PS/DVI files. This could be
1320 ;; switched on automatically depending on the value of
1321 ;; `view-read-only'.
bde04ea9 1322 (set (make-local-variable 'view-read-only) nil)
06a21f70 1323 (run-mode-hooks 'doc-view-mode-hook)))
937cb3fb
GM
1324
1325;;;###autoload
1326(define-minor-mode doc-view-minor-mode
1327 "Toggle Doc view minor mode.
1328With arg, turn Doc view minor mode on if arg is positive, off otherwise.
1329See the command `doc-view-mode' for more information on this mode."
1330 nil " DocView" doc-view-minor-mode-map
1331 :group 'doc-view
1332 (when doc-view-minor-mode
1333 (add-hook 'change-major-mode-hook (lambda () (doc-view-minor-mode -1)) nil t)
1334 (message
1335 "%s"
1336 (substitute-command-keys
1337 "Type \\[doc-view-toggle-display] to toggle between editing or viewing the document."))))
94dbe99c
TTN
1338
1339(defun doc-view-clear-cache ()
1340 "Delete the whole cache (`doc-view-cache-directory')."
1341 (interactive)
515357c2 1342 (dired-delete-file doc-view-cache-directory 'always))
94dbe99c
TTN
1343
1344(defun doc-view-dired-cache ()
1345 "Open `dired' in `doc-view-cache-directory'."
1346 (interactive)
1347 (dired doc-view-cache-directory))
1348
94dbe99c 1349
1666a6b3
TH
1350;;;; Bookmark integration
1351
c123f7fe
GM
1352(declare-function bookmark-make-record-default "bookmark"
1353 (&optional point-only))
a9f8b49b 1354(declare-function bookmark-prop-get "bookmark" (bookmark prop))
43f8b275 1355(declare-function bookmark-default-handler "bookmark" (bmk))
a9f8b49b 1356
32a091dd 1357(defun doc-view-bookmark-make-record ()
43f8b275
SM
1358 (nconc (bookmark-make-record-default)
1359 `((page . ,(doc-view-current-page))
1360 (handler . doc-view-bookmark-jump))))
1666a6b3 1361
069b4ce3 1362
1666a6b3
TH
1363;;;###autoload
1364(defun doc-view-bookmark-jump (bmk)
03e26a79 1365 ;; This implements the `handler' function interface for record type
e0385bf4 1366 ;; returned by `doc-view-bookmark-make-record', which see.
43f8b275
SM
1367 (prog1 (bookmark-default-handler bmk)
1368 (let ((page (bookmark-prop-get bmk 'page)))
1666a6b3 1369 (when (not (eq major-mode 'doc-view-mode))
43f8b275 1370 (doc-view-toggle-display))
a9f8b49b
SM
1371 (with-selected-window
1372 (or (get-buffer-window (current-buffer) 0)
1373 (selected-window))
43f8b275 1374 (doc-view-goto-page page)))))
1666a6b3 1375
069b4ce3
GM
1376
1377(provide 'doc-view)
1378
1379;; Local Variables:
1380;; mode: outline-minor
1381;; End:
1382
aab2236f 1383;; arch-tag: 5d6e5c5e-095f-489e-b4e4-1ca90a7d79be
94dbe99c 1384;;; doc-view.el ends here