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