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