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