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