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