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