* lisp/image-mode.el (image-mode-winprops): Add winprops to
[bpt/emacs.git] / lisp / doc-view.el
CommitLineData
b38b1ec0
SM
1;;; doc-view.el --- View PDF/PostScript/DVI files in Emacs -*- lexical-binding: t -*-
2
ab422c4d 3;; Copyright (C) 2007-2013 Free Software Foundation, Inc.
94dbe99c 4;;
db8a5a18
TH
5;; Author: Tassilo Horn <tsdh@gnu.org>
6;; Maintainer: Tassilo Horn <tsdh@gnu.org>
94dbe99c 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;;
db8a5a18
TH
59;; DocView lets you select a slice of the displayed pages. This slice
60;; will be remembered and applied to all pages of the current
61;; document. This enables you to cut away the margins of a document
62;; to save some space. To select a slice you can use
63;; `doc-view-set-slice' (bound to `s s') which will query you for the
64;; coordinates of the slice's top-left corner and its width and
65;; height. A much more convenient way to do the same is offered by
66;; the command `doc-view-set-slice-using-mouse' (bound to `s m').
67;; After invocation you only have to press mouse-1 at the top-left
68;; corner and drag it to the bottom-right corner of the desired slice.
69;; Even more accurate and convenient is to use
70;; `doc-view-set-slice-from-bounding-box' (bound to `s b') which uses
71;; the BoundingBox information of the current page to set an optimal
72;; slice. To reset the slice use `doc-view-reset-slice' (bound to `s
73;; r').
94dbe99c 74;;
94dbe99c
TTN
75;; You can also search within the document. The command `doc-view-search'
76;; (bound to `C-s') queries for a search regexp and initializes a list of all
77;; matching pages and messages how many match-pages were found. After that you
7baca0fa
JL
78;; can jump to the next page containing a match with an additional `C-s'. With
79;; `C-r' you can do the same, but backwards. To search for a new regexp give a
80;; prefix arg to one of the search functions, e.g. by typing `C-u C-s'. The
81;; searching works by using a plain text representation of the document. If
a8ce3d17 82;; that doesn't already exist the first invocation of `doc-view-search' (or
7baca0fa
JL
83;; `doc-view-search-backward') starts the conversion. When that finishes and
84;; you're still viewing the document (i.e. you didn't switch to another buffer)
85;; you're queried for the regexp then.
640602f7
RS
86;;
87;; Dired users can simply hit `v' on a document file. If it's a PS, PDF or DVI
88;; it will be opened using `doc-view-mode'.
89;;
94dbe99c
TTN
90
91;;; Configuration:
92
640602f7
RS
93;; If the images are too small or too big you should set the "-rXXX" option in
94;; `doc-view-ghostscript-options' to another value. (The bigger your screen,
95;; the higher the value.)
94dbe99c
TTN
96;;
97;; This and all other options can be set with the customization interface.
98;; Simply do
99;;
100;; M-x customize-group RET doc-view RET
101;;
102;; and modify them to your needs.
103
34065e5e 104;;; Todo:
94dbe99c 105
56741510
SM
106;; - add print command.
107;; - share more code with image-mode.
f4c75497 108;; - better menu.
f4c75497 109;; - Bind slicing to a drag event.
cec1df02 110;; - zoom the region around the cursor (like xdvi).
c17587fe
SM
111;; - get rid of the silly arrow in the fringe.
112;; - improve anti-aliasing (pdf-utils gets it better).
f4c75497 113
34065e5e
JL
114;;;; About isearch support
115
116;; I tried implementing isearch by setting
117;; `isearch-search-fun-function' buffer-locally, but that didn't
118;; work too good. The function doing the real search was called
119;; endlessly somehow. But even if we'd get that working no real
120;; isearch feeling comes up due to the missing match highlighting.
121;; Currently I display all lines containing a match in a tooltip and
122;; each C-s or C-r jumps directly to the next/previous page with a
123;; match. With isearch we could only display the current match. So
124;; we had to decide if another C-s jumps to the next page with a
125;; match (thus only the first match in a page will be displayed in a
126;; tooltip) or to the next match, which would do nothing visible
127;; (except the tooltip) if the next match is on the same page.
128
129;; And it's much slower than the current search facility, because
ee7683eb 130;; isearch really searches for each step forward or backward whereas
34065e5e
JL
131;; the current approach searches once and then it knows to which
132;; pages to jump.
133
134;; Anyway, if someone with better isearch knowledge wants to give it a try,
135;; feel free to do it. --Tassilo
136
137;;; Code:
138
f58e0fd5 139(eval-when-compile (require 'cl-lib))
94dbe99c 140(require 'dired)
414dd971 141(require 'image-mode)
7baca0fa 142(require 'jka-compr)
94dbe99c
TTN
143
144;;;; Customization Options
145
146(defgroup doc-view nil
2b541f9a 147 "In-buffer viewer for PDF, PostScript, DVI, and DJVU files."
94dbe99c
TTN
148 :link '(function-link doc-view)
149 :version "22.2"
150 :group 'applications
ff90f4b0 151 :group 'data
94dbe99c
TTN
152 :group 'multimedia
153 :prefix "doc-view-")
154
d0af9f77 155(defcustom doc-view-ghostscript-program "gs"
94dbe99c 156 "Program to convert PS and PDF files to PNG."
c9a9a5e3 157 :type 'file
94dbe99c
TTN
158 :group 'doc-view)
159
bbc7ff25
SM
160(defcustom doc-view-pdfdraw-program
161 (cond
162 ((executable-find "pdfdraw") "pdfdraw")
163 (t "mudraw"))
164 "Name of MuPDF's program to convert PDF files to PNG."
85f17e35
EP
165 :type 'file
166 :version "24.4")
167
168(defcustom doc-view-pdf->png-converter-function
bbc7ff25
SM
169 (if (executable-find doc-view-pdfdraw-program)
170 #'doc-view-pdf->png-converter-mupdf
171 #'doc-view-pdf->png-converter-ghostscript)
85f17e35
EP
172 "Function to call to convert a PDF file into a PNG file."
173 :type '(radio
174 (function-item doc-view-pdf->png-converter-ghostscript
175 :doc "Use ghostscript")
176 (function-item doc-view-pdf->png-converter-mupdf
177 :doc "Use mupdf")
178 function)
179 :version "24.4")
180
94dbe99c 181(defcustom doc-view-ghostscript-options
6a658a30 182 '("-dSAFER" ;; Avoid security problems when rendering files from untrusted
94d11cb5 183 ;; sources.
6a658a30 184 "-dNOPAUSE" "-sDEVICE=png16m" "-dTextAlphaBits=4"
05477667 185 "-dBATCH" "-dGraphicsAlphaBits=4" "-dQUIET")
4b378e75 186 "A list of options to give to ghostscript."
c9a9a5e3 187 :type '(repeat string)
94dbe99c
TTN
188 :group 'doc-view)
189
05477667
SM
190(defcustom doc-view-resolution 100
191 "Dots per inch resolution used to render the documents.
192Higher values result in larger images."
9efa445f
DN
193 :type 'number
194 :group 'doc-view)
05477667 195
96dd18b1
SM
196(defcustom doc-view-scale-internally t
197 "Whether we should try to rescale images ourselves.
198If nil, the document is re-rendered every time the scaling factor is modified.
199This only has an effect if the image libraries linked with Emacs support
200scaling."
201 :type 'boolean)
202
875c044a
TH
203(defcustom doc-view-image-width 850
204 "Default image width.
96dd18b1
SM
205Has only an effect if `doc-view-scale-internally' is non-nil and support for
206scaling is compiled into emacs."
2bed3f04 207 :version "24.1"
875c044a
TH
208 :type 'number
209 :group 'doc-view)
210
d0af9f77 211(defcustom doc-view-dvipdfm-program "dvipdfm"
94dbe99c
TTN
212 "Program to convert DVI files to PDF.
213
214DVI file will be converted to PDF before the resulting PDF is
53d4c024
TH
215converted to PNG.
216
217If this and `doc-view-dvipdf-program' are set,
218`doc-view-dvipdf-program' will be preferred."
219 :type 'file
220 :group 'doc-view)
221
d0af9f77 222(defcustom doc-view-dvipdf-program "dvipdf"
53d4c024
TH
223 "Program to convert DVI files to PDF.
224
225DVI file will be converted to PDF before the resulting PDF is
226converted to PNG.
227
228If this and `doc-view-dvipdfm-program' are set,
229`doc-view-dvipdf-program' will be preferred."
c9a9a5e3 230 :type 'file
94dbe99c
TTN
231 :group 'doc-view)
232
f63f9398
TH
233(define-obsolete-variable-alias 'doc-view-unoconv-program
234 'doc-view-odf->pdf-converter-program
235 "24.4")
236
237(defcustom doc-view-odf->pdf-converter-program
238 (cond
239 ((executable-find "soffice") "soffice")
240 ((executable-find "unoconv") "unoconv")
241 (t "soffice"))
95e16d17
TH
242 "Program to convert any file type readable by OpenOffice.org to PDF.
243
244Needed for viewing OpenOffice.org (and MS Office) files."
f63f9398 245 :version "24.4"
95e16d17
TH
246 :type 'file
247 :group 'doc-view)
248
f63f9398
TH
249(defcustom doc-view-odf->pdf-converter-function
250 (cond
251 ((string-match "unoconv\\'" doc-view-odf->pdf-converter-program)
252 #'doc-view-odf->pdf-converter-unoconv)
253 ((string-match "soffice\\'" doc-view-odf->pdf-converter-program)
254 #'doc-view-odf->pdf-converter-soffice))
255 "Function to call to convert a ODF file into a PDF file."
256 :type '(radio
257 (function-item doc-view-odf->pdf-converter-unoconv
258 :doc "Use unoconv")
259 (function-item doc-view-odf->pdf-converter-soffice
260 :doc "Use LibreOffice")
261 function)
262 :version "24.4")
263
d0af9f77 264(defcustom doc-view-ps2pdf-program "ps2pdf"
94dbe99c
TTN
265 "Program to convert PS files to PDF.
266
267PS files will be converted to PDF before searching is possible."
c9a9a5e3 268 :type 'file
94dbe99c
TTN
269 :group 'doc-view)
270
d0af9f77 271(defcustom doc-view-pdftotext-program "pdftotext"
94dbe99c
TTN
272 "Program to convert PDF files to plain text.
273
274Needed for searching."
c9a9a5e3 275 :type 'file
94dbe99c
TTN
276 :group 'doc-view)
277
c17587fe 278(defcustom doc-view-cache-directory
8aafd651 279 (expand-file-name (format "docview%d" (user-uid))
bce6be12 280 temporary-file-directory)
94dbe99c 281 "The base directory, where the PNG images will be saved."
c9a9a5e3 282 :type 'directory
94dbe99c
TTN
283 :group 'doc-view)
284
56741510
SM
285(defvar doc-view-conversion-buffer " *doc-view conversion output*"
286 "The buffer where messages from the converter programs go to.")
94dbe99c 287
56741510 288(defcustom doc-view-conversion-refresh-interval 1
1f75e53d
GM
289 "Interval in seconds between refreshes of the DocView buffer while converting.
290After such a refresh newly converted pages will be available for
94dbe99c
TTN
291viewing. If set to nil there won't be any refreshes and the
292pages won't be displayed before conversion of the whole document
293has finished."
c9a9a5e3 294 :type 'integer
94dbe99c
TTN
295 :group 'doc-view)
296
0a745733 297(defcustom doc-view-continuous nil
aefcadb6
JL
298 "In Continuous mode reaching the page edge advances to next/previous page.
299When non-nil, scrolling a line upward at the bottom edge of the page
300moves to the next page, and scrolling a line downward at the top edge
301of the page moves to the previous page."
302 :type 'boolean
303 :group 'doc-view
304 :version "23.2")
305
94dbe99c
TTN
306;;;; Internal Variables
307
f440830d
GM
308(defvar doc-view-current-converter-processes nil
309 "Only used internally.")
310(make-variable-buffer-local 'doc-view-current-converter-processes)
311
de171465 312(defun doc-view-new-window-function (winprops)
83600dc8
SM
313 ;; (message "New window %s for buf %s" (car winprops) (current-buffer))
314 (cl-assert (or (eq t (car winprops))
315 (eq (window-buffer (car winprops)) (current-buffer))))
de171465
SM
316 (let ((ol (image-mode-window-get 'overlay winprops)))
317 (if ol
0bca393f 318 (progn
83600dc8
SM
319 (setq ol (copy-overlay ol))
320 ;; `ol' might actually be dead.
321 (move-overlay ol (point-min) (point-max)))
de171465
SM
322 (setq ol (make-overlay (point-min) (point-max) nil t))
323 (overlay-put ol 'doc-view t))
324 (overlay-put ol 'window (car winprops))
83600dc8
SM
325 (unless (windowp (car winprops))
326 ;; It's a pseudo entry. Let's make sure it's not displayed (the
327 ;; `window' property is only effective if its value is a window).
328 (cl-assert (eq t (car winprops)))
329 (delete-overlay ol))
f35ffe5e 330 (image-mode-window-put 'overlay ol winprops)
50105835
SM
331 (when (and (windowp (car winprops))
332 (stringp (overlay-get ol 'display))
333 (null doc-view-current-converter-processes))
334 ;; We're not displaying an image yet, so let's do so. This happens when
335 ;; the buffer is displayed for the first time.
336 ;; Don't do it if there's a conversion is running, since in that case, it
337 ;; will be done later.
338 (with-selected-window (car winprops)
339 (doc-view-goto-page 1)))))
94dbe99c 340
de171465 341(defvar doc-view-current-files nil
94dbe99c 342 "Only used internally.")
ec4853ab 343(make-variable-buffer-local 'doc-view-current-files)
94dbe99c 344
94dbe99c
TTN
345(defvar doc-view-current-timer nil
346 "Only used internally.")
ec4853ab 347(make-variable-buffer-local 'doc-view-current-timer)
94dbe99c 348
94dbe99c
TTN
349(defvar doc-view-current-cache-dir nil
350 "Only used internally.")
ec4853ab 351(make-variable-buffer-local 'doc-view-current-cache-dir)
94dbe99c
TTN
352
353(defvar doc-view-current-search-matches nil
354 "Only used internally.")
ec4853ab 355(make-variable-buffer-local 'doc-view-current-search-matches)
94dbe99c 356
d99abf1b
RS
357(defvar doc-view-pending-cache-flush nil
358 "Only used internally.")
94dbe99c 359
937cb3fb 360(defvar doc-view-previous-major-mode nil
640602f7
RS
361 "Only used internally.")
362
39a402e3
TH
363(defvar doc-view-buffer-file-name nil
364 "Only used internally.
365The file name used for conversion. Normally it's the same as
366`buffer-file-name', but for remote files, compressed files and
367files inside an archive it is a temporary copy of
368the (uncompressed, extracted) file residing in
369`doc-view-cache-directory'.")
370
eb79098b
SM
371(defvar doc-view-doc-type nil
372 "The type of document in the current buffer.
373Can be `dvi', `pdf', or `ps'.")
374
2b541f9a
EP
375(defvar doc-view-single-page-converter-function nil
376 "Function to call to convert a single page of the document to a bitmap file.
377May operate on the source document or on some intermediate (typically PDF)
378conversion of it.")
379
bbc7ff25 380(defvar-local doc-view--image-type nil
2b541f9a
EP
381 "The type of image in the current buffer.
382Can be `png' or `tiff'.")
383
bbc7ff25
SM
384(defvar-local doc-view--image-file-pattern nil
385 "The `format' pattern of image file names.
386Typically \"page-%s.png\".")
2b541f9a 387
640602f7 388;;;; DocView Keymaps
94dbe99c
TTN
389
390(defvar doc-view-mode-map
391 (let ((map (make-sparse-keymap)))
d2d7e96c 392 (set-keymap-parent map image-mode-map)
94dbe99c
TTN
393 ;; Navigation in the document
394 (define-key map (kbd "n") 'doc-view-next-page)
395 (define-key map (kbd "p") 'doc-view-previous-page)
eb8d0216
SM
396 (define-key map (kbd "<next>") 'forward-page)
397 (define-key map (kbd "<prior>") 'backward-page)
398 (define-key map [remap forward-page] 'doc-view-next-page)
399 (define-key map [remap backward-page] 'doc-view-previous-page)
94dbe99c 400 (define-key map (kbd "SPC") 'doc-view-scroll-up-or-next-page)
9cec74cf 401 (define-key map (kbd "S-SPC") 'doc-view-scroll-down-or-previous-page)
94dbe99c 402 (define-key map (kbd "DEL") 'doc-view-scroll-down-or-previous-page)
aefcadb6
JL
403 (define-key map (kbd "C-n") 'doc-view-next-line-or-next-page)
404 (define-key map (kbd "<down>") 'doc-view-next-line-or-next-page)
405 (define-key map (kbd "C-p") 'doc-view-previous-line-or-previous-page)
406 (define-key map (kbd "<up>") 'doc-view-previous-line-or-previous-page)
94dbe99c
TTN
407 (define-key map (kbd "M-<") 'doc-view-first-page)
408 (define-key map (kbd "M->") 'doc-view-last-page)
7656fe61 409 (define-key map [remap goto-line] 'doc-view-goto-page)
4376876e 410 (define-key map (kbd "RET") 'image-next-line)
05477667
SM
411 ;; Zoom in/out.
412 (define-key map "+" 'doc-view-enlarge)
413 (define-key map "-" 'doc-view-shrink)
d7b89879
TH
414 ;; Fit the image to the window
415 (define-key map "W" 'doc-view-fit-width-to-window)
416 (define-key map "H" 'doc-view-fit-height-to-window)
417 (define-key map "P" 'doc-view-fit-page-to-window)
d2d7e96c 418 ;; Killing the buffer (and the process)
94dbe99c 419 (define-key map (kbd "k") 'doc-view-kill-proc-and-buffer)
937cb3fb 420 (define-key map (kbd "K") 'doc-view-kill-proc)
94dbe99c
TTN
421 ;; Slicing the image
422 (define-key map (kbd "s s") 'doc-view-set-slice)
423 (define-key map (kbd "s m") 'doc-view-set-slice-using-mouse)
db8a5a18 424 (define-key map (kbd "s b") 'doc-view-set-slice-from-bounding-box)
94dbe99c
TTN
425 (define-key map (kbd "s r") 'doc-view-reset-slice)
426 ;; Searching
427 (define-key map (kbd "C-s") 'doc-view-search)
428 (define-key map (kbd "<find>") 'doc-view-search)
7baca0fa 429 (define-key map (kbd "C-r") 'doc-view-search-backward)
94dbe99c
TTN
430 ;; Show the tooltip
431 (define-key map (kbd "C-t") 'doc-view-show-tooltip)
640602f7
RS
432 ;; Toggle between text and image display or editing
433 (define-key map (kbd "C-c C-c") 'doc-view-toggle-display)
515357c2
TH
434 ;; Open a new buffer with doc's text contents
435 (define-key map (kbd "C-c C-t") 'doc-view-open-text)
0bca393f
SM
436 ;; Reconvert the current document. Don't just use revert-buffer
437 ;; because that resets the scale factor, the page number, ...
438 (define-key map (kbd "g") 'doc-view-revert-buffer)
439 (define-key map (kbd "r") 'doc-view-revert-buffer)
94dbe99c 440 map)
640602f7
RS
441 "Keymap used by `doc-view-mode' when displaying a doc as a set of images.")
442
0bca393f
SM
443(defun doc-view-revert-buffer (&optional ignore-auto noconfirm)
444 "Like `revert-buffer', but preserves the buffer's current modes."
445 ;; FIXME: this should probably be moved to files.el and used for
446 ;; most/all "g" bindings to revert-buffer.
447 (interactive (list (not current-prefix-arg)))
448 (revert-buffer ignore-auto noconfirm 'preserve-modes))
449
450
f4c75497
SM
451(easy-menu-define doc-view-menu doc-view-mode-map
452 "Menu for Doc View mode."
453 '("DocView"
0a745733
JL
454 ["Toggle display" doc-view-toggle-display]
455 ("Continuous"
456 ["Off" (setq doc-view-continuous nil)
457 :style radio :selected (eq doc-view-continuous nil)]
458 ["On" (setq doc-view-continuous t)
459 :style radio :selected (eq doc-view-continuous t)]
460 "---"
461 ["Save as Default"
462 (customize-save-variable 'doc-view-continuous doc-view-continuous) t]
463 )
464 "---"
f4c75497 465 ["Set Slice" doc-view-set-slice-using-mouse]
db8a5a18 466 ["Set Slice (BoundingBox)" doc-view-set-slice-from-bounding-box]
f4c75497
SM
467 ["Set Slice (manual)" doc-view-set-slice]
468 ["Reset Slice" doc-view-reset-slice]
469 "---"
470 ["Search" doc-view-search]
7baca0fa 471 ["Search Backwards" doc-view-search-backward]
f4c75497
SM
472 ))
473
937cb3fb 474(defvar doc-view-minor-mode-map
640602f7
RS
475 (let ((map (make-sparse-keymap)))
476 ;; Toggle between text and image display or editing
477 (define-key map (kbd "C-c C-c") 'doc-view-toggle-display)
640602f7 478 map)
937cb3fb 479 "Keymap used by `doc-minor-view-mode'.")
94dbe99c
TTN
480
481;;;; Navigation Commands
482
160dfe43
SM
483(defmacro doc-view-current-page (&optional win)
484 `(image-mode-window-get 'page ,win))
de171465
SM
485(defmacro doc-view-current-info () `(image-mode-window-get 'info))
486(defmacro doc-view-current-overlay () `(image-mode-window-get 'overlay))
487(defmacro doc-view-current-image () `(image-mode-window-get 'image))
488(defmacro doc-view-current-slice () `(image-mode-window-get 'slice))
489
bdd42899
SM
490(defun doc-view-last-page-number ()
491 (length doc-view-current-files))
492
94dbe99c
TTN
493(defun doc-view-goto-page (page)
494 "View the page given by PAGE."
495 (interactive "nPage: ")
e7a1c32d 496 (let ((len (doc-view-last-page-number)))
94dbe99c 497 (if (< page 1)
1ca678aa 498 (setq page 1)
56741510
SM
499 (when (and (> page len)
500 ;; As long as the converter is running, we don't know
501 ;; how many pages will be available.
ec4853ab 502 (null doc-view-current-converter-processes))
1ca678aa 503 (setq page len)))
de171465
SM
504 (setf (doc-view-current-page) page
505 (doc-view-current-info)
1ca678aa
MC
506 (concat
507 (propertize
de171465 508 (format "Page %d of %d." page len) 'face 'bold)
1ca678aa 509 ;; Tell user if converting isn't finished yet
ec4853ab 510 (if doc-view-current-converter-processes
1ca678aa
MC
511 " (still converting...)\n"
512 "\n")
513 ;; Display context infos if this page matches the last search
514 (when (and doc-view-current-search-matches
de171465 515 (assq page doc-view-current-search-matches))
1ca678aa
MC
516 (concat (propertize "Search matches:\n" 'face 'bold)
517 (let ((contexts ""))
de171465 518 (dolist (m (cdr (assq page
1ca678aa
MC
519 doc-view-current-search-matches)))
520 (setq contexts (concat contexts " - \"" m "\"\n")))
521 contexts)))))
94dbe99c 522 ;; Update the buffer
56741510
SM
523 ;; We used to find the file name from doc-view-current-files but
524 ;; that's not right if the pages are not generated sequentially
525 ;; or if the page isn't in doc-view-current-files yet.
2b541f9a 526 (let ((file (expand-file-name
bbc7ff25 527 (format doc-view--image-file-pattern page)
2b541f9a 528 (doc-view-current-cache-dir))))
ec4853ab
SM
529 (doc-view-insert-image file :pointer 'arrow)
530 (when (and (not (file-exists-p file))
531 doc-view-current-converter-processes)
532 ;; The PNG file hasn't been generated yet.
2b541f9a
EP
533 (funcall doc-view-single-page-converter-function
534 doc-view-buffer-file-name file page
535 (let ((win (selected-window)))
536 (lambda ()
537 (and (eq (current-buffer) (window-buffer win))
538 ;; If we changed page in the mean
539 ;; time, don't mess things up.
540 (eq (doc-view-current-page win) page)
541 ;; Make sure we don't infloop.
542 (file-readable-p file)
543 (with-selected-window win
544 (doc-view-goto-page page))))))))
de171465 545 (overlay-put (doc-view-current-overlay)
f35ffe5e 546 'help-echo (doc-view-current-info))))
94dbe99c
TTN
547
548(defun doc-view-next-page (&optional arg)
549 "Browse ARG pages forward."
550 (interactive "p")
de171465 551 (doc-view-goto-page (+ (doc-view-current-page) (or arg 1))))
94dbe99c
TTN
552
553(defun doc-view-previous-page (&optional arg)
554 "Browse ARG pages backward."
555 (interactive "p")
de171465 556 (doc-view-goto-page (- (doc-view-current-page) (or arg 1))))
94dbe99c
TTN
557
558(defun doc-view-first-page ()
559 "View the first page."
560 (interactive)
561 (doc-view-goto-page 1))
562
563(defun doc-view-last-page ()
564 "View the last page."
565 (interactive)
bdd42899 566 (doc-view-goto-page (doc-view-last-page-number)))
94dbe99c 567
7d6b4d3c
JL
568(defun doc-view-scroll-up-or-next-page (&optional arg)
569 "Scroll page up ARG lines if possible, else goto next page.
0a745733 570When `doc-view-continuous' is non-nil, scrolling upward
7d6b4d3c
JL
571at the bottom edge of the page moves to the next page.
572Otherwise, goto next page only on typing SPC (ARG is nil)."
573 (interactive "P")
0a745733 574 (if (or doc-view-continuous (null arg))
7d6b4d3c
JL
575 (let ((hscroll (window-hscroll))
576 (cur-page (doc-view-current-page)))
577 (when (= (window-vscroll) (image-scroll-up arg))
578 (doc-view-next-page)
579 (when (/= cur-page (doc-view-current-page))
580 (image-bob)
581 (image-bol 1))
582 (set-window-hscroll (selected-window) hscroll)))
583 (image-scroll-up arg)))
584
585(defun doc-view-scroll-down-or-previous-page (&optional arg)
586 "Scroll page down ARG lines if possible, else goto previous page.
0a745733 587When `doc-view-continuous' is non-nil, scrolling downward
7d6b4d3c
JL
588at the top edge of the page moves to the previous page.
589Otherwise, goto previous page only on typing DEL (ARG is nil)."
590 (interactive "P")
0a745733 591 (if (or doc-view-continuous (null arg))
7d6b4d3c
JL
592 (let ((hscroll (window-hscroll))
593 (cur-page (doc-view-current-page)))
594 (when (= (window-vscroll) (image-scroll-down arg))
595 (doc-view-previous-page)
596 (when (/= cur-page (doc-view-current-page))
597 (image-eob)
598 (image-bol 1))
599 (set-window-hscroll (selected-window) hscroll)))
600 (image-scroll-down arg)))
601
602(defun doc-view-next-line-or-next-page (&optional arg)
603 "Scroll upward by ARG lines if possible, else goto next page.
0a745733 604When `doc-view-continuous' is non-nil, scrolling a line upward
7d6b4d3c 605at the bottom edge of the page moves to the next page."
aefcadb6 606 (interactive "p")
0a745733 607 (if doc-view-continuous
aefcadb6
JL
608 (let ((hscroll (window-hscroll))
609 (cur-page (doc-view-current-page)))
7d6b4d3c 610 (when (= (window-vscroll) (image-next-line arg))
aefcadb6
JL
611 (doc-view-next-page)
612 (when (/= cur-page (doc-view-current-page))
613 (image-bob)
614 (image-bol 1))
615 (set-window-hscroll (selected-window) hscroll)))
616 (image-next-line 1)))
617
7d6b4d3c
JL
618(defun doc-view-previous-line-or-previous-page (&optional arg)
619 "Scroll downward by ARG lines if possible, else goto previous page.
0a745733 620When `doc-view-continuous' is non-nil, scrolling a line downward
aefcadb6
JL
621at the top edge of the page moves to the previous page."
622 (interactive "p")
0a745733 623 (if doc-view-continuous
aefcadb6
JL
624 (let ((hscroll (window-hscroll))
625 (cur-page (doc-view-current-page)))
7d6b4d3c 626 (when (= (window-vscroll) (image-previous-line arg))
aefcadb6
JL
627 (doc-view-previous-page)
628 (when (/= cur-page (doc-view-current-page))
629 (image-eob)
630 (image-bol 1))
631 (set-window-hscroll (selected-window) hscroll)))
7d6b4d3c 632 (image-previous-line arg)))
aefcadb6 633
937cb3fb
GM
634;;;; Utility Functions
635
640602f7 636(defun doc-view-kill-proc ()
ec4853ab 637 "Kill the current converter process(es)."
640602f7 638 (interactive)
bdd42899 639 (while (consp doc-view-current-converter-processes)
83600dc8
SM
640 (ignore-errors ;; Some entries might not be processes, and maybe
641 ;; some are dead already?
ec4853ab 642 (kill-process (pop doc-view-current-converter-processes))))
640602f7
RS
643 (when doc-view-current-timer
644 (cancel-timer doc-view-current-timer)
645 (setq doc-view-current-timer nil))
646 (setq mode-line-process nil))
647
94dbe99c
TTN
648(defun doc-view-kill-proc-and-buffer ()
649 "Kill the current converter process and buffer."
650 (interactive)
640602f7 651 (doc-view-kill-proc)
94dbe99c 652 (when (eq major-mode 'doc-view-mode)
94dbe99c
TTN
653 (kill-buffer (current-buffer))))
654
c17587fe
SM
655(defun doc-view-make-safe-dir (dir)
656 (condition-case nil
657 (let ((umask (default-file-modes)))
c82f64de
LMI
658 (unwind-protect
659 (progn
660 ;; Create temp files with strict access rights. It's easy to
661 ;; loosen them later, whereas it's impossible to close the
662 ;; time-window of loose permissions otherwise.
663 (set-default-file-modes #o0700)
664 (make-directory dir))
665 ;; Reset the umask.
666 (set-default-file-modes umask)))
c17587fe 667 (file-already-exists
c82f64de
LMI
668 (when (file-symlink-p dir)
669 (error "Danger: %s points to a symbolic link" dir))
c17587fe
SM
670 ;; In case it was created earlier with looser rights.
671 ;; We could check the mode info returned by file-attributes, but it's
672 ;; a pain to parse and it may not tell you what we want under
673 ;; non-standard file-systems. So let's just say what we want and let
674 ;; the underlying C code and file-system figure it out.
675 ;; This also ends up checking a bunch of useful conditions: it makes
676 ;; sure we have write-access to the directory and that we own it, thus
677 ;; closing a bunch of security holes.
c82f64de
LMI
678 (condition-case error
679 (set-file-modes dir #o0700)
680 (file-error
681 (error
682 (format "Unable to use temporary directory %s: %s"
683 dir (mapconcat 'identity (cdr error) " "))))))))
c17587fe 684
937cb3fb
GM
685(defun doc-view-current-cache-dir ()
686 "Return the directory where the png files of the current doc should be saved.
687It's a subdirectory of `doc-view-cache-directory'."
688 (if doc-view-current-cache-dir
689 doc-view-current-cache-dir
c17587fe
SM
690 ;; Try and make sure doc-view-cache-directory exists and is safe.
691 (doc-view-make-safe-dir doc-view-cache-directory)
692 ;; Now compute the subdirectory to use.
937cb3fb
GM
693 (setq doc-view-current-cache-dir
694 (file-name-as-directory
c17587fe 695 (expand-file-name
4fa60c54
EP
696 (concat (subst-char-in-string ?% ?_ ;; bug#13679
697 (file-name-nondirectory doc-view-buffer-file-name))
39a402e3
TH
698 "-"
699 (let ((file doc-view-buffer-file-name))
700 (with-temp-buffer
86903c81 701 (set-buffer-multibyte nil)
39a402e3
TH
702 (insert-file-contents-literally file)
703 (md5 (current-buffer)))))
c17587fe 704 doc-view-cache-directory)))))
937cb3fb
GM
705
706(defun doc-view-remove-if (predicate list)
707 "Return LIST with all items removed that satisfy PREDICATE."
708 (let (new-list)
8eda563d 709 (dolist (item list)
937cb3fb 710 (when (not (funcall predicate item))
8eda563d
JB
711 (setq new-list (cons item new-list))))
712 (nreverse new-list)))
937cb3fb 713
789ab9d4
RS
714;;;###autoload
715(defun doc-view-mode-p (type)
95e16d17
TH
716 "Return non-nil if document type TYPE is available for `doc-view'.
717Document types are symbols like `dvi', `ps', `pdf', or `odf' (any
718OpenDocument format)."
789ab9d4 719 (and (display-graphic-p)
c44d54b3
TH
720 (or (image-type-available-p 'imagemagick)
721 (image-type-available-p 'png))
789ab9d4
RS
722 (cond
723 ((eq type 'dvi)
724 (and (doc-view-mode-p 'pdf)
53d4c024
TH
725 (or (and doc-view-dvipdf-program
726 (executable-find doc-view-dvipdf-program))
727 (and doc-view-dvipdfm-program
728 (executable-find doc-view-dvipdfm-program)))))
bbc7ff25 729 ((memq type '(postscript ps eps pdf))
85f17e35 730 ;; FIXME: allow mupdf here
789ab9d4
RS
731 (and doc-view-ghostscript-program
732 (executable-find doc-view-ghostscript-program)))
95e16d17 733 ((eq type 'odf)
f63f9398
TH
734 (and doc-view-odf->pdf-converter-program
735 (executable-find doc-view-odf->pdf-converter-program)
95e16d17 736 (doc-view-mode-p 'pdf)))
2b541f9a
EP
737 ((eq type 'djvu)
738 (executable-find "ddjvu"))
789ab9d4
RS
739 (t ;; unknown image type
740 nil))))
741
94dbe99c
TTN
742;;;; Conversion Functions
743
05477667
SM
744(defvar doc-view-shrink-factor 1.125)
745
746(defun doc-view-enlarge (factor)
83600dc8 747 "Enlarge the document by FACTOR."
05477667 748 (interactive (list doc-view-shrink-factor))
96dd18b1
SM
749 (if (and doc-view-scale-internally
750 (eq (plist-get (cdr (doc-view-current-image)) :type)
751 'imagemagick))
83600dc8
SM
752 ;; ImageMagick supports on-the-fly-rescaling.
753 (let ((new (ceiling (* factor doc-view-image-width))))
754 (unless (equal new doc-view-image-width)
bbc7ff25 755 (setq-local doc-view-image-width new)
83600dc8
SM
756 (doc-view-insert-image
757 (plist-get (cdr (doc-view-current-image)) :file)
758 :width doc-view-image-width)))
759 (let ((new (ceiling (* factor doc-view-resolution))))
760 (unless (equal new doc-view-resolution)
bbc7ff25 761 (setq-local doc-view-resolution new)
83600dc8 762 (doc-view-reconvert-doc)))))
05477667
SM
763
764(defun doc-view-shrink (factor)
765 "Shrink the document."
766 (interactive (list doc-view-shrink-factor))
767 (doc-view-enlarge (/ 1.0 factor)))
768
d7b89879
TH
769(defun doc-view-fit-width-to-window ()
770 "Fit the image width to the window width."
771 (interactive)
772 (let ((win-width (- (nth 2 (window-inside-pixel-edges))
773 (nth 0 (window-inside-pixel-edges))))
774 (slice (doc-view-current-slice)))
775 (if (not slice)
776 (let ((img-width (car (image-display-size
777 (image-get-display-property) t))))
778 (doc-view-enlarge (/ (float win-width) (float img-width))))
779
780 ;; If slice is set
781 (let* ((slice-width (nth 2 slice))
782 (scale-factor (/ (float win-width) (float slice-width)))
783 (new-slice (mapcar (lambda (x) (ceiling (* scale-factor x))) slice)))
784
785 (doc-view-enlarge scale-factor)
786 (setf (doc-view-current-slice) new-slice)
787 (doc-view-goto-page (doc-view-current-page))))))
788
789(defun doc-view-fit-height-to-window ()
790 "Fit the image height to the window height."
791 (interactive)
792 (let ((win-height (- (nth 3 (window-inside-pixel-edges))
793 (nth 1 (window-inside-pixel-edges))))
794 (slice (doc-view-current-slice)))
795 (if (not slice)
796 (let ((img-height (cdr (image-display-size
797 (image-get-display-property) t))))
798 ;; When users call 'doc-view-fit-height-to-window',
799 ;; they might want to go to next page by typing SPC
800 ;; ONLY once. So I used '(- win-height 1)' instead of
801 ;; 'win-height'
802 (doc-view-enlarge (/ (float (- win-height 1)) (float img-height))))
803
804 ;; If slice is set
805 (let* ((slice-height (nth 3 slice))
806 (scale-factor (/ (float (- win-height 1)) (float slice-height)))
807 (new-slice (mapcar (lambda (x) (ceiling (* scale-factor x))) slice)))
808
809 (doc-view-enlarge scale-factor)
810 (setf (doc-view-current-slice) new-slice)
811 (doc-view-goto-page (doc-view-current-page))))))
812
813(defun doc-view-fit-page-to-window ()
814 "Fit the image to the window.
815More specifically, this function enlarges image by:
816
817min {(window-width / image-width), (window-height / image-height)} times."
818 (interactive)
819 (let ((win-width (- (nth 2 (window-inside-pixel-edges))
820 (nth 0 (window-inside-pixel-edges))))
821 (win-height (- (nth 3 (window-inside-pixel-edges))
822 (nth 1 (window-inside-pixel-edges))))
823 (slice (doc-view-current-slice)))
824 (if (not slice)
825 (let ((img-width (car (image-display-size
826 (image-get-display-property) t)))
827 (img-height (cdr (image-display-size
828 (image-get-display-property) t))))
829 (doc-view-enlarge (min (/ (float win-width) (float img-width))
83600dc8
SM
830 (/ (float (- win-height 1))
831 (float img-height)))))
d7b89879
TH
832 ;; If slice is set
833 (let* ((slice-width (nth 2 slice))
834 (slice-height (nth 3 slice))
835 (scale-factor (min (/ (float win-width) (float slice-width))
83600dc8
SM
836 (/ (float (- win-height 1))
837 (float slice-height))))
d7b89879
TH
838 (new-slice (mapcar (lambda (x) (ceiling (* scale-factor x))) slice)))
839 (doc-view-enlarge scale-factor)
840 (setf (doc-view-current-slice) new-slice)
841 (doc-view-goto-page (doc-view-current-page))))))
842
c17587fe 843(defun doc-view-reconvert-doc ()
640602f7
RS
844 "Reconvert the current document.
845Should be invoked when the cached images aren't up-to-date."
846 (interactive)
f4c75497
SM
847 (doc-view-kill-proc)
848 ;; Clear the old cached files
849 (when (file-exists-p (doc-view-current-cache-dir))
bdd42899 850 (delete-directory (doc-view-current-cache-dir) 'recursive))
83600dc8 851 (kill-local-variable 'doc-view-last-page-number)
c17587fe 852 (doc-view-initiate-display))
94dbe99c 853
b4cb319f
SM
854(defun doc-view-sentinel (proc event)
855 "Generic sentinel for doc-view conversion processes."
94dbe99c 856 (if (not (string-match "finished" event))
b4cb319f 857 (message "DocView: process %s changed status to %s."
405ee48d
CY
858 (process-name proc)
859 (if (string-match "\\(.+\\)\n?\\'" event)
860 (match-string 1 event)
861 event))
ec4853ab
SM
862 (when (buffer-live-p (process-get proc 'buffer))
863 (with-current-buffer (process-get proc 'buffer)
864 (setq doc-view-current-converter-processes
865 (delq proc doc-view-current-converter-processes))
866 (setq mode-line-process
867 (if doc-view-current-converter-processes
868 (format ":%s" (car doc-view-current-converter-processes))))
869 (funcall (process-get proc 'callback))))))
870
871(defun doc-view-start-process (name program args callback)
3c03f2ce
TH
872 ;; Make sure the process is started in an existing directory, (rather than
873 ;; some file-name-handler-managed dir, for example).
efb3f01d
SM
874 (let* ((default-directory (or (unhandled-file-name-directory
875 default-directory)
3c03f2ce 876 (expand-file-name "~/")))
ec4853ab
SM
877 (proc (apply 'start-process name doc-view-conversion-buffer
878 program args)))
879 (push proc doc-view-current-converter-processes)
880 (setq mode-line-process (list (format ":%s" proc)))
881 (set-process-sentinel proc 'doc-view-sentinel)
882 (process-put proc 'buffer (current-buffer))
883 (process-put proc 'callback callback)))
b4cb319f
SM
884
885(defun doc-view-dvi->pdf (dvi pdf callback)
886 "Convert DVI to PDF asynchronously and call CALLBACK when finished."
53d4c024
TH
887 ;; Prefer dvipdf over dvipdfm, because the latter has problems if the DVI
888 ;; references and includes other PS files.
889 (if (and doc-view-dvipdf-program
890 (executable-find doc-view-dvipdf-program))
891 (doc-view-start-process "dvi->pdf" doc-view-dvipdf-program
94d11cb5
IK
892 (list dvi pdf)
893 callback)
53d4c024
TH
894 (doc-view-start-process "dvi->pdf" doc-view-dvipdfm-program
895 (list "-o" pdf dvi)
896 callback)))
ec4853ab 897
bbc7ff25
SM
898(defun doc-view-pdf->png-converter-ghostscript (pdf png page callback)
899 (doc-view-start-process
900 "pdf/ps->png" doc-view-ghostscript-program
901 `(,@doc-view-ghostscript-options
902 ,(format "-r%d" (round doc-view-resolution))
903 ,@(if page `(,(format "-dFirstPage=%d" page)))
904 ,@(if page `(,(format "-dLastPage=%d" page)))
905 ,(concat "-sOutputFile=" png)
906 ,pdf)
907 callback))
85f17e35
EP
908
909(defalias 'doc-view-ps->png-converter-ghostscript
910 'doc-view-pdf->png-converter-ghostscript)
911
bbc7ff25
SM
912(defun doc-view-djvu->tiff-converter-ddjvu (djvu tiff page callback)
913 "Convert PAGE of a DJVU file to bitmap(s) asynchronously.
914Call CALLBACK with no arguments when done.
915If PAGE is nil, convert the whole document."
916 (doc-view-start-process
917 "djvu->tiff" "ddjvu"
918 `("-format=tiff"
919 ;; ddjvu only accepts the range 1-999.
920 ,(format "-scale=%d" (round doc-view-resolution))
921 ;; -eachpage was only added after djvulibre-3.5.25.3!
922 ,@(unless page '("-eachpage"))
923 ,@(if page `(,(format "-page=%d" page)))
924 ,djvu
925 ,tiff)
926 callback))
927
928(defun doc-view-pdf->png-converter-mupdf (pdf png page callback)
929 (doc-view-start-process
930 "pdf->png" doc-view-pdfdraw-program
931 `(,(concat "-o" png)
932 ,(format "-r%d" (round doc-view-resolution))
933 ,pdf
934 ,@(if page `(,(format "%d" page))))
935 callback))
85f17e35 936
f63f9398 937(defun doc-view-odf->pdf-converter-unoconv (odf callback)
95e16d17
TH
938 "Convert ODF to PDF asynchronously and call CALLBACK when finished.
939The converted PDF is put into the current cache directory, and it
940is named like ODF with the extension turned to pdf."
f63f9398 941 (doc-view-start-process "odf->pdf" doc-view-odf->pdf-converter-program
95e16d17
TH
942 (list "-f" "pdf" "-o" (doc-view-current-cache-dir) odf)
943 callback))
94dbe99c 944
f63f9398
TH
945(defun doc-view-odf->pdf-converter-soffice (odf callback)
946 "Convert ODF to PDF asynchronously and call CALLBACK when finished.
947The converted PDF is put into the current cache directory, and it
948is named like ODF with the extension turned to pdf."
957a6f26
TH
949 ;; FIXME: soffice doesn't work when there's another running
950 ;; LibreOffice instance, in which case it returns success without
951 ;; actually doing anything. See LibreOffice bug
952 ;; https://bugs.freedesktop.org/show_bug.cgi?id=37531. A workaround
953 ;; is to start soffice with a separate UserInstallation directory.
954 (let ((tmp-user-install-dir (make-temp-file "libreoffice-docview" t)))
955 (doc-view-start-process "odf->pdf" doc-view-odf->pdf-converter-program
956 (list
957 (concat "-env:UserInstallation=file://"
958 tmp-user-install-dir)
959 "--headless" "--convert-to" "pdf"
960 "--outdir" (doc-view-current-cache-dir) odf)
961 (lambda ()
962 (delete-directory tmp-user-install-dir t)
963 (funcall callback)))))
f63f9398 964
94dbe99c 965(defun doc-view-pdf/ps->png (pdf-ps png)
2b541f9a 966 ;; FIXME: Fix name and docstring to account for djvu&tiff.
1f75e53d 967 "Convert PDF-PS to PNG asynchronously."
bbc7ff25
SM
968 (funcall
969 (pcase doc-view-doc-type
970 (`pdf doc-view-pdf->png-converter-function)
971 (`djvu #'doc-view-djvu->tiff-converter-ddjvu)
972 (_ #'doc-view-ps->png-converter-ghostscript))
973 pdf-ps png nil
94d11cb5 974 (let ((resolution doc-view-resolution))
bdd42899
SM
975 (lambda ()
976 ;; Only create the resolution file when it's all done, so it also
977 ;; serves as a witness that the conversion is complete.
978 (write-region (prin1-to-string resolution) nil
979 (expand-file-name "resolution.el"
980 (doc-view-current-cache-dir))
981 nil 'silently)
982 (when doc-view-current-timer
983 (cancel-timer doc-view-current-timer)
984 (setq doc-view-current-timer nil))
bbc7ff25
SM
985 (doc-view-display (current-buffer) 'force))))
986
ec4853ab 987 ;; Update the displayed pages as soon as they're done generating.
94dbe99c
TTN
988 (when doc-view-conversion-refresh-interval
989 (setq doc-view-current-timer
ec4853ab
SM
990 (run-at-time "1 secs" doc-view-conversion-refresh-interval
991 'doc-view-display
992 (current-buffer)))))
993
aa360da1
GM
994(declare-function clear-image-cache "image.c" (&optional filter))
995
72781fef 996(defun doc-view-document->bitmap (pdf png pages)
bbc7ff25 997 "Convert a document file to bitmap images asynchronously.
ec4853ab
SM
998Start by converting PAGES, and then the rest."
999 (if (null pages)
1000 (doc-view-pdf/ps->png pdf png)
1001 ;; We could render several `pages' with a single process if they're
1002 ;; (almost) consecutive, but since in 99% of the cases, there'll be only
1003 ;; a single page anyway, and of the remaining 1%, few cases will have
1004 ;; consecutive pages, it's not worth the trouble.
94d11cb5 1005 (let ((rest (cdr pages)))
72781fef 1006 (funcall doc-view-single-page-converter-function
2b541f9a 1007 pdf (format png (car pages)) (car pages)
ec4853ab
SM
1008 (lambda ()
1009 (if rest
bbc7ff25 1010 (doc-view-document->bitmap pdf png rest)
ec4853ab
SM
1011 ;; Yippie, the important pages are done, update the display.
1012 (clear-image-cache)
bdd42899
SM
1013 ;; For the windows that have a message (like "Welcome to
1014 ;; DocView") display property, clearing the image cache is
1015 ;; not sufficient.
1016 (dolist (win (get-buffer-window-list (current-buffer) nil 'visible))
1017 (with-selected-window win
2167b7b2 1018 (when (stringp (overlay-get (doc-view-current-overlay) 'display))
2b541f9a 1019 (doc-view-goto-page (doc-view-current-page)))))
ec4853ab
SM
1020 ;; Convert the rest of the pages.
1021 (doc-view-pdf/ps->png pdf png)))))))
94dbe99c 1022
b4cb319f
SM
1023(defun doc-view-pdf->txt (pdf txt callback)
1024 "Convert PDF to TXT asynchronously and call CALLBACK when finished."
d0af9f77 1025 (or (executable-find doc-view-pdftotext-program)
ca32d854 1026 (error "You need the `pdftotext' program to convert a PDF to text"))
ec4853ab
SM
1027 (doc-view-start-process "pdf->txt" doc-view-pdftotext-program
1028 (list "-raw" pdf txt)
1029 callback))
515357c2 1030
c00ebc98
TH
1031(defun doc-view-current-cache-doc-pdf ()
1032 "Return the name of the doc.pdf in the current cache dir.
1033 This file exists only if the current document isn't a PDF or PS file already."
1034 (expand-file-name "doc.pdf" (doc-view-current-cache-dir)))
1035
b4cb319f
SM
1036(defun doc-view-doc->txt (txt callback)
1037 "Convert the current document to text and call CALLBACK when done."
7edd6b92 1038 (make-directory (doc-view-current-cache-dir) t)
f58e0fd5
SM
1039 (pcase doc-view-doc-type
1040 (`pdf
b4cb319f
SM
1041 ;; Doc is a PDF, so convert it to TXT
1042 (doc-view-pdf->txt doc-view-buffer-file-name txt callback))
f58e0fd5 1043 (`ps
b4cb319f
SM
1044 ;; Doc is a PS, so convert it to PDF (which will be converted to
1045 ;; TXT thereafter).
c00ebc98 1046 (let ((pdf (doc-view-current-cache-doc-pdf)))
b4cb319f
SM
1047 (doc-view-ps->pdf doc-view-buffer-file-name pdf
1048 (lambda () (doc-view-pdf->txt pdf txt callback)))))
f58e0fd5 1049 (`dvi
b4cb319f
SM
1050 ;; Doc is a DVI. This means that a doc.pdf already exists in its
1051 ;; cache subdirectory.
c00ebc98 1052 (doc-view-pdf->txt (doc-view-current-cache-doc-pdf) txt callback))
f58e0fd5 1053 (`odf
b71b7803
TH
1054 ;; Doc is some ODF (or MS Office) doc. This means that a doc.pdf
1055 ;; already exists in its cache subdirectory.
c00ebc98 1056 (doc-view-pdf->txt (doc-view-current-cache-doc-pdf) txt callback))
f58e0fd5 1057 (_ (error "DocView doesn't know what to do"))))
b4cb319f
SM
1058
1059(defun doc-view-ps->pdf (ps pdf callback)
1060 "Convert PS to PDF asynchronously and call CALLBACK when finished."
d0af9f77 1061 (or (executable-find doc-view-ps2pdf-program)
ca32d854 1062 (error "You need the `ps2pdf' program to convert PS to PDF"))
ec4853ab
SM
1063 (doc-view-start-process "ps->pdf" doc-view-ps2pdf-program
1064 (list
1065 ;; Avoid security problems when rendering files from
1066 ;; untrusted sources.
1067 "-dSAFER"
1068 ;; in-file and out-file
1069 ps pdf)
1070 callback))
1071
1072(defun doc-view-active-pages ()
1073 (let ((pages ()))
1074 (dolist (win (get-buffer-window-list (current-buffer) nil 'visible))
1075 (let ((page (image-mode-window-get 'page win)))
1076 (unless (memq page pages) (push page pages))))
1077 pages))
94dbe99c 1078
640602f7 1079(defun doc-view-convert-current-doc ()
39a402e3 1080 "Convert `doc-view-buffer-file-name' to a set of png files, one file per page.
640602f7
RS
1081Those files are saved in the directory given by the function
1082`doc-view-current-cache-dir'."
c17587fe
SM
1083 ;; Let stale files still display while we recompute the new ones, so only
1084 ;; flush the cache when the conversion is over. One of the reasons why it
1085 ;; is important to keep displaying the stale page is so that revert-buffer
1086 ;; preserves the horizontal/vertical scroll settings (which are otherwise
1087 ;; resets during the redisplay).
1088 (setq doc-view-pending-cache-flush t)
2b541f9a 1089 (let ((png-file (expand-file-name
bbc7ff25 1090 (format doc-view--image-file-pattern "%d")
2b541f9a 1091 (doc-view-current-cache-dir))))
7edd6b92 1092 (make-directory (doc-view-current-cache-dir) t)
f58e0fd5
SM
1093 (pcase doc-view-doc-type
1094 (`dvi
eb79098b
SM
1095 ;; DVI files have to be converted to PDF before Ghostscript can process
1096 ;; it.
c00ebc98 1097 (let ((pdf (doc-view-current-cache-doc-pdf)))
b4cb319f
SM
1098 (doc-view-dvi->pdf doc-view-buffer-file-name pdf
1099 (lambda () (doc-view-pdf/ps->png pdf png-file)))))
f58e0fd5 1100 (`odf
95e16d17
TH
1101 ;; ODF files have to be converted to PDF before Ghostscript can
1102 ;; process it.
c00ebc98 1103 (let ((pdf (doc-view-current-cache-doc-pdf))
2b541f9a
EP
1104 (opdf (expand-file-name
1105 (concat (file-name-base doc-view-buffer-file-name)
1106 ".pdf")
1107 doc-view-current-cache-dir))
e95a67dc 1108 (png-file png-file))
2b541f9a 1109 ;; The unoconv tool only supports an output directory, but no
95e16d17
TH
1110 ;; file name. It's named like the input file with the
1111 ;; extension replaced by pdf.
f63f9398 1112 (funcall doc-view-odf->pdf-converter-function doc-view-buffer-file-name
95e16d17
TH
1113 (lambda ()
1114 ;; Rename to doc.pdf
1115 (rename-file opdf pdf)
1116 (doc-view-pdf/ps->png pdf png-file)))))
bbc7ff25 1117 ((or `pdf `djvu)
2b541f9a 1118 (let ((pages (doc-view-active-pages)))
bbc7ff25 1119 ;; Convert doc to bitmap images starting with the active pages.
72781fef 1120 (doc-view-document->bitmap doc-view-buffer-file-name png-file pages)))
f58e0fd5 1121 (_
eb79098b
SM
1122 ;; Convert to PNG images.
1123 (doc-view-pdf/ps->png doc-view-buffer-file-name png-file)))))
94dbe99c 1124
94dbe99c
TTN
1125;;;; Slicing
1126
aa360da1
GM
1127(declare-function image-size "image.c" (spec &optional pixels frame))
1128
94dbe99c
TTN
1129(defun doc-view-set-slice (x y width height)
1130 "Set the slice of the images that should be displayed.
1131You can use this function to tell doc-view not to display the
1132margins of the document. It prompts for the top-left corner (X
1133and Y) of the slice to display and its WIDTH and HEIGHT.
1134
db8a5a18
TH
1135See `doc-view-set-slice-using-mouse' and
1136`doc-view-set-slice-from-bounding-box' for more convenient ways
1137to do that. To reset the slice use `doc-view-reset-slice'."
94dbe99c 1138 (interactive
de171465 1139 (let* ((size (image-size (doc-view-current-image) t))
1ca678aa
MC
1140 (a (read-number (format "Top-left X (0..%d): " (car size))))
1141 (b (read-number (format "Top-left Y (0..%d): " (cdr size))))
1142 (c (read-number (format "Width (0..%d): " (- (car size) a))))
1143 (d (read-number (format "Height (0..%d): " (- (cdr size) b)))))
94dbe99c 1144 (list a b c d)))
de171465 1145 (setf (doc-view-current-slice) (list x y width height))
94dbe99c 1146 ;; Redisplay
de171465 1147 (doc-view-goto-page (doc-view-current-page)))
94dbe99c
TTN
1148
1149(defun doc-view-set-slice-using-mouse ()
1150 "Set the slice of the images that should be displayed.
1151You set the slice by pressing mouse-1 at its top-left corner and
1152dragging it to its bottom-right corner. See also
1153`doc-view-set-slice' and `doc-view-reset-slice'."
1154 (interactive)
1155 (let (x y w h done)
1156 (while (not done)
1157 (let ((e (read-event
1ca678aa
MC
1158 (concat "Press mouse-1 at the top-left corner and "
1159 "drag it to the bottom-right corner!"))))
1160 (when (eq (car e) 'drag-mouse-1)
1161 (setq x (car (posn-object-x-y (event-start e))))
1162 (setq y (cdr (posn-object-x-y (event-start e))))
1163 (setq w (- (car (posn-object-x-y (event-end e))) x))
1164 (setq h (- (cdr (posn-object-x-y (event-end e))) y))
1165 (setq done t))))
94dbe99c
TTN
1166 (doc-view-set-slice x y w h)))
1167
db8a5a18
TH
1168(defun doc-view-get-bounding-box ()
1169 "Get the BoundingBox information of the current page."
1170 (let* ((page (doc-view-current-page))
c00ebc98
TH
1171 (doc (let ((cache-doc (doc-view-current-cache-doc-pdf)))
1172 (if (file-exists-p cache-doc)
1173 cache-doc
1174 doc-view-buffer-file-name)))
db8a5a18
TH
1175 (o (shell-command-to-string
1176 (concat doc-view-ghostscript-program
1177 " -dSAFER -dBATCH -dNOPAUSE -q -sDEVICE=bbox "
1178 (format "-dFirstPage=%s -dLastPage=%s %s"
c00ebc98 1179 page page doc)))))
db8a5a18
TH
1180 (save-match-data
1181 (when (string-match (concat "%%BoundingBox: "
1182 "\\([[:digit:]]+\\) \\([[:digit:]]+\\) "
1183 "\\([[:digit:]]+\\) \\([[:digit:]]+\\)") o)
1184 (mapcar #'string-to-number
1185 (list (match-string 1 o)
1186 (match-string 2 o)
1187 (match-string 3 o)
1188 (match-string 4 o)))))))
1189
1190(defvar doc-view-paper-sizes
1191 '((a4 595 842)
1192 (a4-landscape 842 595)
1193 (letter 612 792)
1194 (letter-landscape 792 612)
1195 (legal 612 1008)
1196 (legal-landscape 1008 612)
1197 (a3 842 1191)
1198 (a3-landscape 1191 842)
1199 (tabloid 792 1224)
1200 (ledger 1224 792))
1201 "An alist from paper size names to dimensions.")
1202
1203(defun doc-view-guess-paper-size (iw ih)
1204 "Guess the paper size according to the aspect ratio."
1205 (cl-labels ((div (x y)
1206 (round (/ (* 100.0 x) y))))
1207 (let ((ar (div iw ih))
1208 (al (mapcar (lambda (l)
46624b4f 1209 (list (div (nth 1 l) (nth 2 l)) (car l)))
db8a5a18
TH
1210 doc-view-paper-sizes)))
1211 (cadr (assoc ar al)))))
1212
1213(defun doc-view-scale-bounding-box (ps iw ih bb)
46624b4f
SM
1214 (list (/ (* (nth 0 bb) iw) (nth 1 (assoc ps doc-view-paper-sizes)))
1215 (/ (* (nth 1 bb) ih) (nth 2 (assoc ps doc-view-paper-sizes)))
1216 (/ (* (nth 2 bb) iw) (nth 1 (assoc ps doc-view-paper-sizes)))
1217 (/ (* (nth 3 bb) ih) (nth 2 (assoc ps doc-view-paper-sizes)))))
db8a5a18
TH
1218
1219(defun doc-view-set-slice-from-bounding-box (&optional force-paper-size)
1220 "Set the slice from the document's BoundingBox information.
1221The result is that the margins are almost completely cropped,
1222much more accurate than could be done manually using
1223`doc-view-set-slice-using-mouse'."
1224 (interactive "P")
1225 (let ((bb (doc-view-get-bounding-box)))
1226 (if (not bb)
1227 (message "BoundingBox couldn't be determined")
1228 (let* ((is (image-size (doc-view-current-image) t))
1229 (iw (car is))
1230 (ih (cdr is))
bbc7ff25
SM
1231 (ps (or (and (null force-paper-size)
1232 (doc-view-guess-paper-size iw ih))
db8a5a18 1233 (intern (completing-read "Paper size: "
bbc7ff25 1234 doc-view-paper-sizes
db8a5a18
TH
1235 nil t))))
1236 (bb (doc-view-scale-bounding-box ps iw ih bb))
1237 (x1 (nth 0 bb))
1238 (y1 (nth 1 bb))
1239 (x2 (nth 2 bb))
1240 (y2 (nth 3 bb)))
1241 ;; We keep a 2 pixel margin.
1242 (doc-view-set-slice (- x1 2) (- ih y2 2)
1243 (+ (- x2 x1) 4) (+ (- y2 y1) 4))))))
1244
94dbe99c 1245(defun doc-view-reset-slice ()
e48a5bf9 1246 "Reset the current slice.
1f75e53d 1247After calling this function whole pages will be visible again."
94dbe99c 1248 (interactive)
de171465 1249 (setf (doc-view-current-slice) nil)
94dbe99c 1250 ;; Redisplay
de171465 1251 (doc-view-goto-page (doc-view-current-page)))
94dbe99c
TTN
1252
1253;;;; Display
1254
1255(defun doc-view-insert-image (file &rest args)
1256 "Insert the given png FILE.
e48a5bf9 1257ARGS is a list of image descriptors."
c17587fe
SM
1258 (when doc-view-pending-cache-flush
1259 (clear-image-cache)
1260 (setq doc-view-pending-cache-flush nil))
d35f5864 1261 (let ((ol (doc-view-current-overlay)))
f35ffe5e
TH
1262 ;; Only insert the image if the buffer is visible.
1263 (when (window-live-p (overlay-get ol 'window))
d35f5864
TH
1264 (let* ((image (if (and file (file-readable-p file))
1265 (if (not (and doc-view-scale-internally
1266 (fboundp 'imagemagick-types)))
1267 (apply 'create-image file doc-view--image-type nil args)
1268 (unless (member :width args)
1269 (setq args `(,@args :width ,doc-view-image-width)))
1270 (apply 'create-image file 'imagemagick nil args))))
1271 (slice (doc-view-current-slice))
1272 (img-width (and image (car (image-size image))))
1273 (displayed-img-width (if (and image slice)
1274 (* (/ (float (nth 2 slice))
1275 (car (image-size image 'pixels)))
1276 img-width)
1277 img-width))
1278 (window-width (window-width (selected-window))))
1279 (setf (doc-view-current-image) image)
1280 (move-overlay ol (point-min) (point-max))
1281 ;; In case the window is wider than the image, center the image
1282 ;; horizontally.
1283 (overlay-put ol 'before-string
1284 (when (and image (> window-width displayed-img-width))
1285 (propertize " " 'display
1286 `(space :align-to (+ center (-0.5 . ,displayed-img-width))))))
1287 (overlay-put ol 'display
1288 (cond
1289 (image
1290 (if slice
1291 (list (cons 'slice slice) image)
1292 image))
1293 ;; We're trying to display a page that doesn't exist.
1294 (doc-view-current-converter-processes
1295 ;; Maybe the page doesn't exist *yet*.
1296 "Cannot display this page (yet)!")
1297 (t
1298 ;; Typically happens if the conversion process somehow
1299 ;; failed. Better not signal an error here because it
1300 ;; could prevent a subsequent reconversion from fixing
1301 ;; the problem.
1302 (concat "Cannot display this page!\n"
1303 "Maybe because of a conversion failure!"))))
1304 (let ((win (overlay-get ol 'window)))
1305 (if (stringp (overlay-get ol 'display))
1306 (progn ;Make sure the text is not scrolled out of view.
1307 (set-window-hscroll win 0)
1308 (set-window-vscroll win 0))
1309 (let ((hscroll (image-mode-window-get 'hscroll win))
1310 (vscroll (image-mode-window-get 'vscroll win)))
1311 ;; Reset scroll settings, in case they were changed.
1312 (if hscroll (set-window-hscroll win hscroll))
1313 (if vscroll (set-window-vscroll win vscroll)))))))))
94dbe99c
TTN
1314
1315(defun doc-view-sort (a b)
1316 "Return non-nil if A should be sorted before B.
1317Predicate for sorting `doc-view-current-files'."
f4c75497
SM
1318 (or (< (length a) (length b))
1319 (and (= (length a) (length b))
1320 (string< a b))))
94dbe99c 1321
65073003
SM
1322(defun doc-view-display (buffer &optional force)
1323 "Start viewing the document in BUFFER.
214abdd4
SM
1324If FORCE is non-nil, start viewing even if the document does not
1325have the page we want to view."
65073003 1326 (with-current-buffer buffer
83600dc8 1327 (let ((prev-pages doc-view-current-files))
56741510
SM
1328 (setq doc-view-current-files
1329 (sort (directory-files (doc-view-current-cache-dir) t
bbc7ff25
SM
1330 (format doc-view--image-file-pattern
1331 "[0-9]+")
2b541f9a 1332 t)
56741510 1333 'doc-view-sort))
5ad86e34
DA
1334 (unless (eq (length prev-pages) (length doc-view-current-files))
1335 (force-mode-line-update))
83600dc8
SM
1336 (dolist (win (or (get-buffer-window-list buffer nil t)
1337 (list t)))
10b6e7c1 1338 (let* ((page (doc-view-current-page win))
2b541f9a 1339 (pagefile (expand-file-name
bbc7ff25 1340 (format doc-view--image-file-pattern page)
2b541f9a 1341 (doc-view-current-cache-dir))))
10b6e7c1
CY
1342 (when (or force
1343 (and (not (member pagefile prev-pages))
1344 (member pagefile doc-view-current-files)))
83600dc8
SM
1345 (if (windowp win)
1346 (with-selected-window win
1347 (cl-assert (eq (current-buffer) buffer) t)
1348 (doc-view-goto-page page))
db8a5a18 1349 (doc-view-goto-page page))))))))
94dbe99c
TTN
1350
1351(defun doc-view-buffer-message ()
c17587fe
SM
1352 ;; Only show this message initially, not when refreshing the buffer (in which
1353 ;; case it's better to keep displaying the "stale" page while computing
1354 ;; the fresh new ones).
de171465
SM
1355 (unless (overlay-get (doc-view-current-overlay) 'display)
1356 (overlay-put (doc-view-current-overlay) 'display
c17587fe
SM
1357 (concat (propertize "Welcome to DocView!" 'face 'bold)
1358 "\n"
1359 "
1f75e53d 1360If you see this buffer it means that the document you want to view is being
f4c75497 1361converted to PNG and the conversion of the first page hasn't finished yet or
94dbe99c
TTN
1362`doc-view-conversion-refresh-interval' is set to nil.
1363
1364For now these keys are useful:
1365
1ca678aa 1366`q' : Bury this buffer. Conversion will go on in background.
937cb3fb 1367`k' : Kill the conversion process and this buffer.
c17587fe 1368`K' : Kill the conversion process.\n"))))
94dbe99c 1369
aa360da1
GM
1370(declare-function tooltip-show "tooltip" (text &optional use-echo-area))
1371
94dbe99c
TTN
1372(defun doc-view-show-tooltip ()
1373 (interactive)
de171465 1374 (tooltip-show (doc-view-current-info)))
94dbe99c 1375
515357c2
TH
1376(defun doc-view-open-text ()
1377 "Open a buffer with the current doc's contents as text."
1378 (interactive)
ec4853ab 1379 (if doc-view-current-converter-processes
515357c2
TH
1380 (message "DocView: please wait till conversion finished.")
1381 (let ((txt (expand-file-name "doc.txt" (doc-view-current-cache-dir))))
1382 (if (file-readable-p txt)
5b355315
TH
1383 (let ((name (concat "Text contents of "
1384 (file-name-nondirectory buffer-file-name)))
1385 (dir (file-name-directory buffer-file-name)))
1386 (with-current-buffer (find-file txt)
1387 (rename-buffer name)
1388 (setq default-directory dir)))
b4cb319f 1389 (doc-view-doc->txt txt 'doc-view-open-text)))))
515357c2 1390
937cb3fb 1391;;;;; Toggle between editing and viewing
640602f7 1392
83600dc8
SM
1393(defvar-local doc-view-saved-settings nil
1394 "Doc-view settings saved while in some other mode.")
1395(put 'doc-view-saved-settings 'permanent-local t)
1396
640602f7 1397(defun doc-view-toggle-display ()
937cb3fb 1398 "Toggle between editing a document as text or viewing it."
640602f7 1399 (interactive)
937cb3fb
GM
1400 (if (eq major-mode 'doc-view-mode)
1401 ;; Switch to editing mode
1402 (progn
1403 (doc-view-kill-proc)
1404 (setq buffer-read-only nil)
ad727c81
TH
1405 ;; Switch to the previously used major mode or fall back to
1406 ;; normal mode.
291cc045 1407 (doc-view-fallback-mode)
c17587fe 1408 (doc-view-minor-mode 1))
937cb3fb
GM
1409 ;; Switch to doc-view-mode
1410 (when (and (buffer-modified-p)
1411 (y-or-n-p "The buffer has been modified. Save the changes? "))
1412 (save-buffer))
937cb3fb 1413 (doc-view-mode)))
640602f7 1414
94dbe99c
TTN
1415;;;; Searching
1416
515357c2 1417
94dbe99c
TTN
1418(defun doc-view-search-internal (regexp file)
1419 "Return a list of FILE's pages that contain text matching REGEXP.
1ca678aa
MC
1420The value is an alist of the form (PAGE CONTEXTS) where PAGE is
1421the pagenumber and CONTEXTS are all lines of text containing a match."
94dbe99c
TTN
1422 (with-temp-buffer
1423 (insert-file-contents file)
1424 (let ((page 1)
1ca678aa
MC
1425 (lastpage 1)
1426 matches)
94dbe99c 1427 (while (re-search-forward (concat "\\(?:\\([\f]\\)\\|\\("
1ca678aa 1428 regexp "\\)\\)") nil t)
069b4ce3 1429 (when (match-string 1) (setq page (1+ page)))
1ca678aa
MC
1430 (when (match-string 2)
1431 (if (/= page lastpage)
c17587fe 1432 (push (cons page
515357c2
TH
1433 (list (buffer-substring
1434 (line-beginning-position)
1435 (line-end-position))))
1436 matches)
1ca678aa
MC
1437 (setq matches (cons
1438 (append
1439 (or
1440 ;; This page already is a match.
1441 (car matches)
1442 ;; This is the first match on page.
1443 (list page))
1444 (list (buffer-substring
1445 (line-beginning-position)
1446 (line-end-position))))
1447 (cdr matches))))
1448 (setq lastpage page)))
94dbe99c
TTN
1449 (nreverse matches))))
1450
1451(defun doc-view-search-no-of-matches (list)
1452 "Extract the number of matches from the search result LIST."
1453 (let ((no 0))
1454 (dolist (p list)
1455 (setq no (+ no (1- (length p)))))
1456 no))
1457
7baca0fa
JL
1458(defun doc-view-search-backward (new-query)
1459 "Call `doc-view-search' for backward search.
1460If prefix NEW-QUERY is given, ask for a new regexp."
1461 (interactive "P")
da99b369 1462 (doc-view-search new-query t))
7baca0fa
JL
1463
1464(defun doc-view-search (new-query &optional backward)
1465 "Jump to the next match or initiate a new search if NEW-QUERY is given.
94dbe99c 1466If the current document hasn't been transformed to plain text
7baca0fa
JL
1467till now do that first.
1468If BACKWARD is non-nil, jump to the previous match."
1469 (interactive "P")
da99b369 1470 (if (and (not new-query)
7baca0fa
JL
1471 doc-view-current-search-matches)
1472 (if backward
1473 (doc-view-search-previous-match 1)
1474 (doc-view-search-next-match 1))
1475 ;; New search, so forget the old results.
1476 (setq doc-view-current-search-matches nil)
1477 (let ((txt (expand-file-name "doc.txt"
1478 (doc-view-current-cache-dir))))
1479 (if (file-readable-p txt)
1480 (progn
1481 (setq doc-view-current-search-matches
1482 (doc-view-search-internal
1483 (read-from-minibuffer "Regexp: ")
1484 txt))
1485 (message "DocView: search yielded %d matches."
1486 (doc-view-search-no-of-matches
1487 doc-view-current-search-matches)))
1488 ;; We must convert to TXT first!
ec4853ab 1489 (if doc-view-current-converter-processes
7baca0fa 1490 (message "DocView: please wait till conversion finished.")
b4cb319f 1491 (doc-view-doc->txt txt (lambda () (doc-view-search nil))))))))
94dbe99c
TTN
1492
1493(defun doc-view-search-next-match (arg)
1494 "Go to the ARGth next matching page."
1495 (interactive "p")
937cb3fb 1496 (let* ((next-pages (doc-view-remove-if
de171465 1497 (lambda (i) (<= (car i) (doc-view-current-page)))
937cb3fb 1498 doc-view-current-search-matches))
1ca678aa 1499 (page (car (nth (1- arg) next-pages))))
94dbe99c 1500 (if page
1ca678aa 1501 (doc-view-goto-page page)
94dbe99c 1502 (when (and
1ca678aa
MC
1503 doc-view-current-search-matches
1504 (y-or-n-p "No more matches after current page. Wrap to first match? "))
1505 (doc-view-goto-page (caar doc-view-current-search-matches))))))
94dbe99c
TTN
1506
1507(defun doc-view-search-previous-match (arg)
1508 "Go to the ARGth previous matching page."
1509 (interactive "p")
937cb3fb 1510 (let* ((prev-pages (doc-view-remove-if
de171465 1511 (lambda (i) (>= (car i) (doc-view-current-page)))
937cb3fb 1512 doc-view-current-search-matches))
1ca678aa 1513 (page (car (nth (1- arg) (nreverse prev-pages)))))
94dbe99c 1514 (if page
1ca678aa 1515 (doc-view-goto-page page)
94dbe99c 1516 (when (and
1ca678aa
MC
1517 doc-view-current-search-matches
1518 (y-or-n-p "No more matches before current page. Wrap to last match? "))
1519 (doc-view-goto-page (caar (last doc-view-current-search-matches)))))))
94dbe99c 1520
640602f7 1521;;;; User interface commands and the mode
94dbe99c 1522
0d17c4b9 1523(put 'doc-view-mode 'mode-class 'special)
c17587fe 1524
515357c2
TH
1525(defun doc-view-already-converted-p ()
1526 "Return non-nil if the current doc was already converted."
1527 (and (file-exists-p (doc-view-current-cache-dir))
bdd42899
SM
1528 ;; Check that the resolution info is there, otherwise it means
1529 ;; the conversion is incomplete.
1530 (file-readable-p (expand-file-name "resolution.el"
1531 (doc-view-current-cache-dir)))
2b541f9a
EP
1532 (> (length (directory-files
1533 (doc-view-current-cache-dir)
bbc7ff25 1534 nil (format doc-view--image-file-pattern "[0-9]+")))
bdd42899 1535 0)))
515357c2 1536
c17587fe 1537(defun doc-view-initiate-display ()
2b541f9a 1538 ;; Switch to image display if possible.
322f4559 1539 (if (doc-view-mode-p doc-view-doc-type)
c17587fe
SM
1540 (progn
1541 (doc-view-buffer-message)
de171465 1542 (setf (doc-view-current-page) (or (doc-view-current-page) 1))
515357c2 1543 (if (doc-view-already-converted-p)
c17587fe
SM
1544 (progn
1545 (message "DocView: using cached files!")
bbc7ff25 1546 ;; Load the saved resolution.
bdd42899
SM
1547 (let* ((res-file (expand-file-name "resolution.el"
1548 (doc-view-current-cache-dir)))
1549 (res
1550 (with-temp-buffer
1551 (when (file-readable-p res-file)
1552 (insert-file-contents res-file)
1553 (read (current-buffer))))))
1554 (when (numberp res)
bbc7ff25 1555 (setq-local doc-view-resolution res)))
65073003 1556 (doc-view-display (current-buffer) 'force))
c17587fe
SM
1557 (doc-view-convert-current-doc))
1558 (message
1559 "%s"
1560 (substitute-command-keys
1561 (concat "Type \\[doc-view-toggle-display] to toggle between "
1562 "editing or viewing the document."))))
1563 (message
1564 "%s"
0855c2ca
CY
1565 (concat "No PNG support is available, or some conversion utility for "
1566 (file-name-extension doc-view-buffer-file-name)
1567 " files is missing."))
1281bd51
TH
1568 (when (and (executable-find doc-view-pdftotext-program)
1569 (y-or-n-p
1570 "Unable to render file. View extracted text instead? "))
1571 (doc-view-open-text))
1572 (doc-view-toggle-display)))
94dbe99c 1573
e0385bf4 1574(defvar bookmark-make-record-function)
069b4ce3 1575
65073003
SM
1576(defun doc-view-clone-buffer-hook ()
1577 ;; FIXME: There are several potential problems linked with reconversion
1578 ;; and auto-revert when we have indirect buffers because they share their
1579 ;; /tmp cache directory. This sharing is good (you'd rather not reconvert
1580 ;; for each clone), but that means that clones need to collaborate a bit.
1581 ;; I guess it mostly means: detect when a reconversion process is already
1582 ;; running, and run the sentinel in all clones.
515357c2 1583 ;;
de171465 1584 ;; Maybe the clones should really have a separate /tmp directory
65073003
SM
1585 ;; so they could have a different resolution and you could use clones
1586 ;; for zooming.
de171465
SM
1587 (remove-overlays (point-min) (point-max) 'doc-view t)
1588 (if (consp image-mode-winprops-alist) (setq image-mode-winprops-alist nil)))
65073003 1589
eb79098b
SM
1590(defun doc-view-intersection (l1 l2)
1591 (let ((l ()))
1592 (dolist (x l1) (if (memq x l2) (push x l)))
1593 l))
1594
291cc045
TH
1595(defun doc-view-set-doc-type ()
1596 "Figure out the current document type (`doc-view-doc-type')."
1597 (let ((name-types
1598 (when buffer-file-name
1599 (cdr (assoc (file-name-extension buffer-file-name)
1600 '(
1601 ;; DVI
1602 ("dvi" dvi)
1603 ;; PDF
1604 ("pdf" pdf) ("epdf" pdf)
1605 ;; PostScript
1606 ("ps" ps) ("eps" ps)
2b541f9a
EP
1607 ;; DjVu
1608 ("djvu" djvu)
291cc045
TH
1609 ;; OpenDocument formats
1610 ("odt" odf) ("ods" odf) ("odp" odf) ("odg" odf)
1611 ("odc" odf) ("odi" odf) ("odm" odf) ("ott" odf)
1612 ("ots" odf) ("otp" odf) ("otg" odf)
1613 ;; Microsoft Office formats (also handled
1614 ;; by the odf conversion chain)
1615 ("doc" odf) ("docx" odf) ("xls" odf) ("xlsx" odf)
1616 ("ppt" odf) ("pptx" odf))))))
1617 (content-types
1618 (save-excursion
1619 (goto-char (point-min))
1620 (cond
1621 ((looking-at "%!") '(ps))
1622 ((looking-at "%PDF") '(pdf))
2b541f9a
EP
1623 ((looking-at "\367\002") '(dvi))
1624 ((looking-at "AT&TFORM") '(djvu))))))
bbc7ff25
SM
1625 (setq-local doc-view-doc-type
1626 (car (or (doc-view-intersection name-types content-types)
1627 (when (and name-types content-types)
1628 (error "Conflicting types: name says %s but content says %s"
1629 name-types content-types))
1630 name-types content-types
1631 (error "Cannot determine the document type"))))))
291cc045 1632
2b541f9a
EP
1633(defun doc-view-set-up-single-converter ()
1634 "Find the right single-page converter for the current document type"
1635 (pcase-let ((`(,conv-function ,type ,extension)
1636 (pcase doc-view-doc-type
bbc7ff25
SM
1637 (`djvu (list #'doc-view-djvu->tiff-converter-ddjvu 'tiff "tif"))
1638 (_ (list doc-view-pdf->png-converter-function 'png "png")))))
2b541f9a
EP
1639 (setq-local doc-view-single-page-converter-function conv-function)
1640 (setq-local doc-view--image-type type)
bbc7ff25 1641 (setq-local doc-view--image-file-pattern (concat "page-%s." extension))))
2b541f9a 1642
57b9823e
TH
1643;; desktop.el integration
1644
50105835 1645(defun doc-view-desktop-save-buffer (_desktop-dirname)
57b9823e
TH
1646 `((page . ,(doc-view-current-page))
1647 (slice . ,(doc-view-current-slice))))
1648
50105835
SM
1649(declare-function desktop-restore-file-buffer "desktop"
1650 (buffer-filename buffer-name buffer-misc))
1651
57b9823e
TH
1652(defun doc-view-restore-desktop-buffer (file name misc)
1653 (let ((page (cdr (assq 'page misc)))
1654 (slice (cdr (assq 'slice misc))))
50105835 1655 (desktop-restore-file-buffer file name misc)
57b9823e
TH
1656 (with-selected-window (or (get-buffer-window (current-buffer) 0)
1657 (selected-window))
1658 (doc-view-goto-page page)
1659 (when slice (apply 'doc-view-set-slice slice)))))
1660
1661(setq desktop-buffer-mode-handlers
1662 (cons '(doc-view-mode . doc-view-restore-desktop-buffer)
1663 desktop-buffer-mode-handlers))
1664
640602f7 1665;;;###autoload
937cb3fb 1666(defun doc-view-mode ()
640602f7 1667 "Major mode in DocView buffers.
06a21f70
TH
1668
1669DocView Mode is an Emacs document viewer. It displays PDF, PS
1670and DVI files (as PNG images) in Emacs buffers.
1671
640602f7 1672You can use \\<doc-view-mode-map>\\[doc-view-toggle-display] to
5c1f16b0
SM
1673toggle between displaying the document or editing it as text.
1674\\{doc-view-mode-map}"
937cb3fb 1675 (interactive)
f9adf05b 1676
96fe38a8 1677 (if (= (point-min) (point-max))
85699772 1678 ;; The doc is empty or doesn't exist at all, so fallback to
96fe38a8
SM
1679 ;; another mode. We used to also check file-exists-p, but this
1680 ;; returns nil for tar members.
291cc045 1681 (doc-view-fallback-mode)
06a21f70 1682
83600dc8 1683 (let* ((prev-major-mode (if (derived-mode-p 'doc-view-mode)
06a21f70 1684 doc-view-previous-major-mode
83600dc8 1685 (unless (eq major-mode 'fundamental-mode)
ad727c81 1686 major-mode))))
06a21f70 1687 (kill-all-local-variables)
bbc7ff25 1688 (setq-local doc-view-previous-major-mode prev-major-mode))
83600dc8
SM
1689
1690 (dolist (var doc-view-saved-settings)
1691 (set (make-local-variable (car var)) (cdr var)))
bc19637d 1692
06a21f70 1693 ;; Figure out the document type.
291cc045
TH
1694 (unless doc-view-doc-type
1695 (doc-view-set-doc-type))
2b541f9a 1696 (doc-view-set-up-single-converter)
bc19637d 1697
06a21f70
TH
1698 (doc-view-make-safe-dir doc-view-cache-directory)
1699 ;; Handle compressed files, remote files, files inside archives
bbc7ff25 1700 (setq-local doc-view-buffer-file-name
06a21f70
TH
1701 (cond
1702 (jka-compr-really-do-compress
5e9fde5e 1703 ;; FIXME: there's a risk of name conflicts here.
06a21f70
TH
1704 (expand-file-name
1705 (file-name-nondirectory
1706 (file-name-sans-extension buffer-file-name))
1707 doc-view-cache-directory))
1708 ;; Is the file readable by local processes?
1709 ;; We used to use `file-remote-p' but it's unclear what it's
1710 ;; supposed to return nil for things like local files accessed via
1711 ;; `su' or via file://...
1712 ((let ((file-name-handler-alist nil))
5e9fde5e
SM
1713 (not (and buffer-file-name (file-readable-p buffer-file-name))))
1714 ;; FIXME: there's a risk of name conflicts here.
06a21f70 1715 (expand-file-name
5e9fde5e
SM
1716 (if buffer-file-name
1717 (file-name-nondirectory buffer-file-name)
1718 (buffer-name))
1719 doc-view-cache-directory))
06a21f70
TH
1720 (t buffer-file-name)))
1721 (when (not (string= doc-view-buffer-file-name buffer-file-name))
1722 (write-region nil nil doc-view-buffer-file-name))
bc19637d 1723
06a21f70
TH
1724 (add-hook 'change-major-mode-hook
1725 (lambda ()
1726 (doc-view-kill-proc)
1727 (remove-overlays (point-min) (point-max) 'doc-view t))
1728 nil t)
1729 (add-hook 'clone-indirect-buffer-hook 'doc-view-clone-buffer-hook nil t)
1730 (add-hook 'kill-buffer-hook 'doc-view-kill-proc nil t)
57b9823e
TH
1731 (when (and (boundp 'desktop-save-mode)
1732 desktop-save-mode)
1733 (setq-local desktop-save-buffer 'doc-view-desktop-save-buffer))
bc19637d 1734
06a21f70
TH
1735 (remove-overlays (point-min) (point-max) 'doc-view t) ;Just in case.
1736 ;; Keep track of display info ([vh]scroll, page number, overlay,
1737 ;; ...) for each window in which this document is shown.
1738 (add-hook 'image-mode-new-window-functions
1739 'doc-view-new-window-function nil t)
1740 (image-mode-setup-winprops)
bc19637d 1741
bbc7ff25
SM
1742 (setq-local mode-line-position
1743 '(" P" (:eval (number-to-string (doc-view-current-page)))
1744 "/" (:eval (number-to-string (doc-view-last-page-number)))))
06a21f70 1745 ;; Don't scroll unless the user specifically asked for it.
bbc7ff25
SM
1746 (setq-local auto-hscroll-mode nil)
1747 (setq-local mwheel-scroll-up-function #'doc-view-scroll-up-or-next-page)
1748 (setq-local mwheel-scroll-down-function
1749 #'doc-view-scroll-down-or-previous-page)
1750 (setq-local cursor-type nil)
06a21f70 1751 (use-local-map doc-view-mode-map)
bbc7ff25
SM
1752 (add-hook 'after-revert-hook 'doc-view-reconvert-doc nil t)
1753 (setq-local bookmark-make-record-function
1754 #'doc-view-bookmark-make-record)
06a21f70
TH
1755 (setq mode-name "DocView"
1756 buffer-read-only t
1757 major-mode 'doc-view-mode)
1758 (doc-view-initiate-display)
d1d33062
TH
1759 ;; Switch off view-mode explicitly, because doc-view-mode is the
1760 ;; canonical view mode for PDF/PS/DVI files. This could be
1761 ;; switched on automatically depending on the value of
1762 ;; `view-read-only'.
bbc7ff25 1763 (setq-local view-read-only nil)
06a21f70 1764 (run-mode-hooks 'doc-view-mode-hook)))
937cb3fb 1765
291cc045
TH
1766(defun doc-view-fallback-mode ()
1767 "Fallback to the previous or next best major mode."
83600dc8
SM
1768 (let ((vars (if (derived-mode-p 'doc-view-mode)
1769 (mapcar (lambda (var) (cons var (symbol-value var)))
1770 '(doc-view-resolution
1771 image-mode-winprops-alist)))))
e7a1c32d 1772 (remove-overlays (point-min) (point-max) 'doc-view t)
83600dc8
SM
1773 (if doc-view-previous-major-mode
1774 (funcall doc-view-previous-major-mode)
1775 (let ((auto-mode-alist
1776 (rassq-delete-all
1777 'doc-view-mode-maybe
1778 (rassq-delete-all 'doc-view-mode
1779 (copy-alist auto-mode-alist)))))
1780 (normal-mode)))
1781 (when vars
1782 (setq-local doc-view-saved-settings vars))))
291cc045
TH
1783
1784;;;###autoload
1785(defun doc-view-mode-maybe ()
1786 "Switch to `doc-view-mode' if possible.
1787If the required external tools are not available, then fallback
1788to the next best mode."
1789 (condition-case nil
1790 (doc-view-set-doc-type)
1791 (error (doc-view-fallback-mode)))
1792 (if (doc-view-mode-p doc-view-doc-type)
1793 (doc-view-mode)
1794 (doc-view-fallback-mode)))
1795
937cb3fb
GM
1796;;;###autoload
1797(define-minor-mode doc-view-minor-mode
06e21633
CY
1798 "Toggle displaying buffer via Doc View (Doc View minor mode).
1799With a prefix argument ARG, enable Doc View minor mode if ARG is
1800positive, and disable it otherwise. If called from Lisp, enable
1801the mode if ARG is omitted or nil.
1802
937cb3fb
GM
1803See the command `doc-view-mode' for more information on this mode."
1804 nil " DocView" doc-view-minor-mode-map
1805 :group 'doc-view
1806 (when doc-view-minor-mode
1807 (add-hook 'change-major-mode-hook (lambda () (doc-view-minor-mode -1)) nil t)
1808 (message
1809 "%s"
1810 (substitute-command-keys
1811 "Type \\[doc-view-toggle-display] to toggle between editing or viewing the document."))))
94dbe99c
TTN
1812
1813(defun doc-view-clear-cache ()
1814 "Delete the whole cache (`doc-view-cache-directory')."
1815 (interactive)
515357c2 1816 (dired-delete-file doc-view-cache-directory 'always))
94dbe99c
TTN
1817
1818(defun doc-view-dired-cache ()
1819 "Open `dired' in `doc-view-cache-directory'."
1820 (interactive)
1821 (dired doc-view-cache-directory))
1822
94dbe99c 1823
1666a6b3
TH
1824;;;; Bookmark integration
1825
e44fa724
KF
1826(declare-function bookmark-make-record-default
1827 "bookmark" (&optional no-file no-context posn))
a9f8b49b 1828(declare-function bookmark-prop-get "bookmark" (bookmark prop))
43f8b275 1829(declare-function bookmark-default-handler "bookmark" (bmk))
a9f8b49b 1830
32a091dd 1831(defun doc-view-bookmark-make-record ()
43f8b275
SM
1832 (nconc (bookmark-make-record-default)
1833 `((page . ,(doc-view-current-page))
1834 (handler . doc-view-bookmark-jump))))
1666a6b3 1835
069b4ce3 1836
1666a6b3
TH
1837;;;###autoload
1838(defun doc-view-bookmark-jump (bmk)
03e26a79 1839 ;; This implements the `handler' function interface for record type
e0385bf4 1840 ;; returned by `doc-view-bookmark-make-record', which see.
43f8b275
SM
1841 (prog1 (bookmark-default-handler bmk)
1842 (let ((page (bookmark-prop-get bmk 'page)))
1666a6b3 1843 (when (not (eq major-mode 'doc-view-mode))
43f8b275 1844 (doc-view-toggle-display))
a9f8b49b 1845 (with-selected-window
94d11cb5
IK
1846 (or (get-buffer-window (current-buffer) 0)
1847 (selected-window))
1848 (doc-view-goto-page page)))))
1666a6b3 1849
069b4ce3
GM
1850
1851(provide 'doc-view)
1852
1853;; Local Variables:
f0da764a 1854;; eval: (outline-minor-mode 1)
069b4ce3
GM
1855;; End:
1856
94dbe99c 1857;;; doc-view.el ends here