doc-view.el 64.7 KB
Newer Older
1 2
;;; doc-view.el --- View PDF/PostScript/DVI files in Emacs -*- lexical-binding: t -*-

Thien-Thi Nguyen's avatar
Thien-Thi Nguyen committed
3

4
;; Copyright (C) 2007-2012 Free Software Foundation, Inc.
Thien-Thi Nguyen's avatar
Thien-Thi Nguyen committed
5
;;
6 7
;; Author: Tassilo Horn <tsdh@gnu.org>
;; Maintainer: Tassilo Horn <tsdh@gnu.org>
Thien-Thi Nguyen's avatar
Thien-Thi Nguyen committed
8 9 10 11
;; Keywords: files, pdf, ps, dvi

;; This file is part of GNU Emacs.

12
;; GNU Emacs is free software: you can redistribute it and/or modify
Thien-Thi Nguyen's avatar
Thien-Thi Nguyen committed
13
;; it under the terms of the GNU General Public License as published by
14 15
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
Thien-Thi Nguyen's avatar
Thien-Thi Nguyen committed
16 17 18 19 20 21 22

;; GNU Emacs is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
23
;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
Thien-Thi Nguyen's avatar
Thien-Thi Nguyen committed
24 25 26

;;; Requirements:

27
;; doc-view.el requires GNU Emacs 22.1 or newer.  You also need Ghostscript,
28 29 30
;; `dvipdf' (comes with Ghostscript) or `dvipdfm' (comes with teTeX or TeXLive)
;; and `pdftotext', which comes with xpdf (http://www.foolabs.com/xpdf/) or
;; poppler (http://poppler.freedesktop.org/).
Thien-Thi Nguyen's avatar
Thien-Thi Nguyen committed
31 32 33 34 35 36 37 38

;;; Commentary:

;; DocView is a document viewer for Emacs.  It converts PDF, PS and DVI files
;; to a set of PNG files, one PNG for each page, and displays the PNG images
;; inside an Emacs buffer.  This buffer uses `doc-view-mode' which provides
;; convenient key bindings for browsing the document.
;;
39
;; To use it simply open a document file with
Thien-Thi Nguyen's avatar
Thien-Thi Nguyen committed
40
;;
41
;;     C-x C-f ~/path/to/document RET
Thien-Thi Nguyen's avatar
Thien-Thi Nguyen committed
42
;;
43 44
;; and the document will be converted and displayed, if your emacs supports png
;; images.  With `C-c C-c' you can toggle between the rendered images
45
;; representation and the source text representation of the document.
Thien-Thi Nguyen's avatar
Thien-Thi Nguyen committed
46 47 48
;;
;; Since conversion may take some time all the PNG images are cached in a
;; subdirectory of `doc-view-cache-directory' and reused when you want to view
49 50
;; that file again.  To reconvert a document hit `g' (`doc-view-reconvert-doc')
;; when displaying the document.  To delete all cached files use
Thien-Thi Nguyen's avatar
Thien-Thi Nguyen committed
51 52 53 54 55 56 57 58 59
;; `doc-view-clear-cache'.  To open the cache with dired, so that you can tidy
;; it out use `doc-view-dired-cache'.
;;
;; When conversion in underway the first page will be displayed as soon as it
;; is available and the available pages are refreshed every
;; `doc-view-conversion-refresh-interval' seconds.  If that variable is nil the
;; pages won't be displayed before conversion of the document finished
;; completely.
;;
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
;; DocView lets you select a slice of the displayed pages.  This slice
;; will be remembered and applied to all pages of the current
;; document.  This enables you to cut away the margins of a document
;; to save some space.  To select a slice you can use
;; `doc-view-set-slice' (bound to `s s') which will query you for the
;; coordinates of the slice's top-left corner and its width and
;; height.  A much more convenient way to do the same is offered by
;; the command `doc-view-set-slice-using-mouse' (bound to `s m').
;; After invocation you only have to press mouse-1 at the top-left
;; corner and drag it to the bottom-right corner of the desired slice.
;; Even more accurate and convenient is to use
;; `doc-view-set-slice-from-bounding-box' (bound to `s b') which uses
;; the BoundingBox information of the current page to set an optimal
;; slice.  To reset the slice use `doc-view-reset-slice' (bound to `s
;; r').
Thien-Thi Nguyen's avatar
Thien-Thi Nguyen committed
75 76 77 78
;;
;; You can also search within the document.  The command `doc-view-search'
;; (bound to `C-s') queries for a search regexp and initializes a list of all
;; matching pages and messages how many match-pages were found.  After that you
79 80 81 82
;; can jump to the next page containing a match with an additional `C-s'.  With
;; `C-r' you can do the same, but backwards.  To search for a new regexp give a
;; prefix arg to one of the search functions, e.g. by typing `C-u C-s'.  The
;; searching works by using a plain text representation of the document.  If
Juanma Barranquero's avatar
Juanma Barranquero committed
83
;; that doesn't already exist the first invocation of `doc-view-search' (or
84 85 86
;; `doc-view-search-backward') starts the conversion.  When that finishes and
;; you're still viewing the document (i.e. you didn't switch to another buffer)
;; you're queried for the regexp then.
87 88 89 90
;;
;; Dired users can simply hit `v' on a document file.  If it's a PS, PDF or DVI
;; it will be opened using `doc-view-mode'.
;;
Thien-Thi Nguyen's avatar
Thien-Thi Nguyen committed
91 92 93

;;; Configuration:

94 95 96
;; If the images are too small or too big you should set the "-rXXX" option in
;; `doc-view-ghostscript-options' to another value.  (The bigger your screen,
;; the higher the value.)
Thien-Thi Nguyen's avatar
Thien-Thi Nguyen committed
97 98 99 100 101 102 103 104
;;
;; This and all other options can be set with the customization interface.
;; Simply do
;;
;;     M-x customize-group RET doc-view RET
;;
;; and modify them to your needs.

105
;;; Todo:
Thien-Thi Nguyen's avatar
Thien-Thi Nguyen committed
106

107 108
;; - add print command.
;; - share more code with image-mode.
109 110
;; - better menu.
;; - Bind slicing to a drag event.
111
;; - zoom the region around the cursor (like xdvi).
112 113
;; - get rid of the silly arrow in the fringe.
;; - improve anti-aliasing (pdf-utils gets it better).
114

115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
;;;; About isearch support

;; I tried implementing isearch by setting
;; `isearch-search-fun-function' buffer-locally, but that didn't
;; work too good.  The function doing the real search was called
;; endlessly somehow.  But even if we'd get that working no real
;; isearch feeling comes up due to the missing match highlighting.
;; Currently I display all lines containing a match in a tooltip and
;; each C-s or C-r jumps directly to the next/previous page with a
;; match.  With isearch we could only display the current match.  So
;; we had to decide if another C-s jumps to the next page with a
;; match (thus only the first match in a page will be displayed in a
;; tooltip) or to the next match, which would do nothing visible
;; (except the tooltip) if the next match is on the same page.

;; And it's much slower than the current search facility, because
Paul Eggert's avatar
Paul Eggert committed
131
;; isearch really searches for each step forward or backward whereas
132 133 134 135 136 137 138 139
;; the current approach searches once and then it knows to which
;; pages to jump.

;; Anyway, if someone with better isearch knowledge wants to give it a try,
;; feel free to do it.  --Tassilo

;;; Code:

Stefan Monnier's avatar
Stefan Monnier committed
140
(eval-when-compile (require 'cl-lib))
Thien-Thi Nguyen's avatar
Thien-Thi Nguyen committed
141
(require 'dired)
142
(require 'image-mode)
143
(require 'jka-compr)
Thien-Thi Nguyen's avatar
Thien-Thi Nguyen committed
144 145 146 147 148 149 150 151

;;;; Customization Options

(defgroup doc-view nil
  "In-buffer viewer for PDF, PostScript and DVI files."
  :link '(function-link doc-view)
  :version "22.2"
  :group 'applications
Chong Yidong's avatar
Chong Yidong committed
152
  :group 'data
Thien-Thi Nguyen's avatar
Thien-Thi Nguyen committed
153 154 155
  :group 'multimedia
  :prefix "doc-view-")

156
(defcustom doc-view-ghostscript-program "gs"
Thien-Thi Nguyen's avatar
Thien-Thi Nguyen committed
157
  "Program to convert PS and PDF files to PNG."
Reiner Steib's avatar
Reiner Steib committed
158
  :type 'file
Thien-Thi Nguyen's avatar
Thien-Thi Nguyen committed
159 160 161
  :group 'doc-view)

(defcustom doc-view-ghostscript-options
162
  '("-dSAFER" ;; Avoid security problems when rendering files from untrusted
163
    ;; sources.
164
    "-dNOPAUSE" "-sDEVICE=png16m" "-dTextAlphaBits=4"
165
    "-dBATCH" "-dGraphicsAlphaBits=4" "-dQUIET")
166
  "A list of options to give to ghostscript."
Reiner Steib's avatar
Reiner Steib committed
167
  :type '(repeat string)
Thien-Thi Nguyen's avatar
Thien-Thi Nguyen committed
168 169
  :group 'doc-view)

170 171 172
(defcustom doc-view-resolution 100
  "Dots per inch resolution used to render the documents.
Higher values result in larger images."
Dan Nicolaescu's avatar
Dan Nicolaescu committed
173 174
  :type 'number
  :group 'doc-view)
175

176 177 178
(defcustom doc-view-image-width 850
  "Default image width.
Has only an effect if imagemagick support is compiled into emacs."
179
  :version "24.1"
180 181 182
  :type 'number
  :group 'doc-view)

183
(defcustom doc-view-dvipdfm-program "dvipdfm"
Thien-Thi Nguyen's avatar
Thien-Thi Nguyen committed
184 185 186
  "Program to convert DVI files to PDF.

DVI file will be converted to PDF before the resulting PDF is
187 188 189 190 191 192 193
converted to PNG.

If this and `doc-view-dvipdf-program' are set,
`doc-view-dvipdf-program' will be preferred."
  :type 'file
  :group 'doc-view)

194
(defcustom doc-view-dvipdf-program "dvipdf"
195 196 197 198 199 200 201
  "Program to convert DVI files to PDF.

DVI file will be converted to PDF before the resulting PDF is
converted to PNG.

If this and `doc-view-dvipdfm-program' are set,
`doc-view-dvipdf-program' will be preferred."
Reiner Steib's avatar
Reiner Steib committed
202
  :type 'file
Thien-Thi Nguyen's avatar
Thien-Thi Nguyen committed
203 204
  :group 'doc-view)

205
(defcustom doc-view-unoconv-program "unoconv"
206 207 208
  "Program to convert any file type readable by OpenOffice.org to PDF.

Needed for viewing OpenOffice.org (and MS Office) files."
209
  :version "24.1"
210 211 212
  :type 'file
  :group 'doc-view)

213
(defcustom doc-view-ps2pdf-program "ps2pdf"
Thien-Thi Nguyen's avatar
Thien-Thi Nguyen committed
214 215 216
  "Program to convert PS files to PDF.

PS files will be converted to PDF before searching is possible."
Reiner Steib's avatar
Reiner Steib committed
217
  :type 'file
Thien-Thi Nguyen's avatar
Thien-Thi Nguyen committed
218 219
  :group 'doc-view)

220
(defcustom doc-view-pdftotext-program "pdftotext"
Thien-Thi Nguyen's avatar
Thien-Thi Nguyen committed
221 222 223
  "Program to convert PDF files to plain text.

Needed for searching."
Reiner Steib's avatar
Reiner Steib committed
224
  :type 'file
Thien-Thi Nguyen's avatar
Thien-Thi Nguyen committed
225 226
  :group 'doc-view)

227
(defcustom doc-view-cache-directory
228
  (expand-file-name (format "docview%d" (user-uid))
229
		    temporary-file-directory)
Thien-Thi Nguyen's avatar
Thien-Thi Nguyen committed
230
  "The base directory, where the PNG images will be saved."
Reiner Steib's avatar
Reiner Steib committed
231
  :type 'directory
Thien-Thi Nguyen's avatar
Thien-Thi Nguyen committed
232 233
  :group 'doc-view)

234 235
(defvar doc-view-conversion-buffer " *doc-view conversion output*"
  "The buffer where messages from the converter programs go to.")
Thien-Thi Nguyen's avatar
Thien-Thi Nguyen committed
236

237
(defcustom doc-view-conversion-refresh-interval 1
238 239
  "Interval in seconds between refreshes of the DocView buffer while converting.
After such a refresh newly converted pages will be available for
Thien-Thi Nguyen's avatar
Thien-Thi Nguyen committed
240 241 242
viewing.  If set to nil there won't be any refreshes and the
pages won't be displayed before conversion of the whole document
has finished."
Reiner Steib's avatar
Reiner Steib committed
243
  :type 'integer
Thien-Thi Nguyen's avatar
Thien-Thi Nguyen committed
244 245
  :group 'doc-view)

Juri Linkov's avatar
Juri Linkov committed
246
(defcustom doc-view-continuous nil
247 248 249 250 251 252 253 254
  "In Continuous mode reaching the page edge advances to next/previous page.
When non-nil, scrolling a line upward at the bottom edge of the page
moves to the next page, and scrolling a line downward at the top edge
of the page moves to the previous page."
  :type 'boolean
  :group 'doc-view
  :version "23.2")

Thien-Thi Nguyen's avatar
Thien-Thi Nguyen committed
255 256
;;;; Internal Variables

257
(defun doc-view-new-window-function (winprops)
258 259 260
  ;; (message "New window %s for buf %s" (car winprops) (current-buffer))
  (cl-assert (or (eq t (car winprops))
                 (eq (window-buffer (car winprops)) (current-buffer))))
261 262
  (let ((ol (image-mode-window-get 'overlay winprops)))
    (if ol
263
        (progn
264 265 266
          (setq ol (copy-overlay ol))
          ;; `ol' might actually be dead.
          (move-overlay ol (point-min) (point-max)))
267 268 269
      (setq ol (make-overlay (point-min) (point-max) nil t))
      (overlay-put ol 'doc-view t))
    (overlay-put ol 'window (car winprops))
270 271 272 273 274
    (unless (windowp (car winprops))
      ;; It's a pseudo entry.  Let's make sure it's not displayed (the
      ;; `window' property is only effective if its value is a window).
      (cl-assert (eq t (car winprops)))
      (delete-overlay ol))
275
    (image-mode-window-put 'overlay ol winprops)))
Thien-Thi Nguyen's avatar
Thien-Thi Nguyen committed
276

277
(defvar doc-view-current-files nil
Thien-Thi Nguyen's avatar
Thien-Thi Nguyen committed
278
  "Only used internally.")
279
(make-variable-buffer-local 'doc-view-current-files)
Thien-Thi Nguyen's avatar
Thien-Thi Nguyen committed
280

281
(defvar doc-view-current-converter-processes nil
Thien-Thi Nguyen's avatar
Thien-Thi Nguyen committed
282
  "Only used internally.")
283
(make-variable-buffer-local 'doc-view-current-converter-processes)
Thien-Thi Nguyen's avatar
Thien-Thi Nguyen committed
284 285 286

(defvar doc-view-current-timer nil
  "Only used internally.")
287
(make-variable-buffer-local 'doc-view-current-timer)
Thien-Thi Nguyen's avatar
Thien-Thi Nguyen committed
288 289 290

(defvar doc-view-current-cache-dir nil
  "Only used internally.")
291
(make-variable-buffer-local 'doc-view-current-cache-dir)
Thien-Thi Nguyen's avatar
Thien-Thi Nguyen committed
292 293 294

(defvar doc-view-current-search-matches nil
  "Only used internally.")
295
(make-variable-buffer-local 'doc-view-current-search-matches)
Thien-Thi Nguyen's avatar
Thien-Thi Nguyen committed
296

297 298
(defvar doc-view-pending-cache-flush nil
  "Only used internally.")
Thien-Thi Nguyen's avatar
Thien-Thi Nguyen committed
299

300
(defvar doc-view-previous-major-mode nil
301 302
  "Only used internally.")

303 304 305 306 307 308 309 310
(defvar doc-view-buffer-file-name nil
  "Only used internally.
The file name used for conversion.  Normally it's the same as
`buffer-file-name', but for remote files, compressed files and
files inside an archive it is a temporary copy of
the (uncompressed, extracted) file residing in
`doc-view-cache-directory'.")

311 312 313 314
(defvar doc-view-doc-type nil
  "The type of document in the current buffer.
Can be `dvi', `pdf', or `ps'.")

315
;;;; DocView Keymaps
Thien-Thi Nguyen's avatar
Thien-Thi Nguyen committed
316 317 318

(defvar doc-view-mode-map
  (let ((map (make-sparse-keymap)))
319
    (set-keymap-parent map image-mode-map)
Thien-Thi Nguyen's avatar
Thien-Thi Nguyen committed
320 321 322
    ;; Navigation in the document
    (define-key map (kbd "n")         'doc-view-next-page)
    (define-key map (kbd "p")         'doc-view-previous-page)
323 324 325 326
    (define-key map (kbd "<next>")    'forward-page)
    (define-key map (kbd "<prior>")   'backward-page)
    (define-key map [remap forward-page]  'doc-view-next-page)
    (define-key map [remap backward-page] 'doc-view-previous-page)
Thien-Thi Nguyen's avatar
Thien-Thi Nguyen committed
327 328
    (define-key map (kbd "SPC")       'doc-view-scroll-up-or-next-page)
    (define-key map (kbd "DEL")       'doc-view-scroll-down-or-previous-page)
329 330 331 332
    (define-key map (kbd "C-n")       'doc-view-next-line-or-next-page)
    (define-key map (kbd "<down>")    'doc-view-next-line-or-next-page)
    (define-key map (kbd "C-p")       'doc-view-previous-line-or-previous-page)
    (define-key map (kbd "<up>")      'doc-view-previous-line-or-previous-page)
Thien-Thi Nguyen's avatar
Thien-Thi Nguyen committed
333 334
    (define-key map (kbd "M-<")       'doc-view-first-page)
    (define-key map (kbd "M->")       'doc-view-last-page)
335
    (define-key map [remap goto-line] 'doc-view-goto-page)
336
    (define-key map (kbd "RET")       'image-next-line)
337 338 339
    ;; Zoom in/out.
    (define-key map "+"               'doc-view-enlarge)
    (define-key map "-"               'doc-view-shrink)
340 341 342 343
    ;; Fit the image to the window
    (define-key map "W"               'doc-view-fit-width-to-window)
    (define-key map "H"               'doc-view-fit-height-to-window)
    (define-key map "P"               'doc-view-fit-page-to-window)
344
    ;; Killing the buffer (and the process)
Thien-Thi Nguyen's avatar
Thien-Thi Nguyen committed
345
    (define-key map (kbd "k")         'doc-view-kill-proc-and-buffer)
346
    (define-key map (kbd "K")         'doc-view-kill-proc)
Thien-Thi Nguyen's avatar
Thien-Thi Nguyen committed
347 348 349
    ;; Slicing the image
    (define-key map (kbd "s s")       'doc-view-set-slice)
    (define-key map (kbd "s m")       'doc-view-set-slice-using-mouse)
350
    (define-key map (kbd "s b")       'doc-view-set-slice-from-bounding-box)
Thien-Thi Nguyen's avatar
Thien-Thi Nguyen committed
351 352 353 354
    (define-key map (kbd "s r")       'doc-view-reset-slice)
    ;; Searching
    (define-key map (kbd "C-s")       'doc-view-search)
    (define-key map (kbd "<find>")    'doc-view-search)
355
    (define-key map (kbd "C-r")       'doc-view-search-backward)
Thien-Thi Nguyen's avatar
Thien-Thi Nguyen committed
356 357
    ;; Show the tooltip
    (define-key map (kbd "C-t")       'doc-view-show-tooltip)
358 359
    ;; Toggle between text and image display or editing
    (define-key map (kbd "C-c C-c")   'doc-view-toggle-display)
360 361
    ;; Open a new buffer with doc's text contents
    (define-key map (kbd "C-c C-t")   'doc-view-open-text)
362 363 364 365
    ;; Reconvert the current document.  Don't just use revert-buffer
    ;; because that resets the scale factor, the page number, ...
    (define-key map (kbd "g")         'doc-view-revert-buffer)
    (define-key map (kbd "r")         'doc-view-revert-buffer)
Thien-Thi Nguyen's avatar
Thien-Thi Nguyen committed
366
    map)
367 368
  "Keymap used by `doc-view-mode' when displaying a doc as a set of images.")

369 370 371 372 373 374 375 376
(defun doc-view-revert-buffer (&optional ignore-auto noconfirm)
  "Like `revert-buffer', but preserves the buffer's current modes."
  ;; FIXME: this should probably be moved to files.el and used for
  ;; most/all "g" bindings to revert-buffer.
  (interactive (list (not current-prefix-arg)))
  (revert-buffer ignore-auto noconfirm 'preserve-modes))


377 378 379
(easy-menu-define doc-view-menu doc-view-mode-map
  "Menu for Doc View mode."
  '("DocView"
Juri Linkov's avatar
Juri Linkov committed
380 381 382 383 384 385 386 387 388 389 390
    ["Toggle display"		doc-view-toggle-display]
    ("Continuous"
     ["Off"                     (setq doc-view-continuous nil)
      :style radio :selected    (eq doc-view-continuous nil)]
     ["On"		        (setq doc-view-continuous t)
      :style radio :selected    (eq doc-view-continuous t)]
     "---"
     ["Save as Default"
      (customize-save-variable 'doc-view-continuous doc-view-continuous) t]
     )
    "---"
391
    ["Set Slice"		doc-view-set-slice-using-mouse]
392
    ["Set Slice (BoundingBox)"  doc-view-set-slice-from-bounding-box]
393 394 395 396
    ["Set Slice (manual)"	doc-view-set-slice]
    ["Reset Slice"		doc-view-reset-slice]
    "---"
    ["Search"			doc-view-search]
397
    ["Search Backwards"         doc-view-search-backward]
398 399
    ))

400
(defvar doc-view-minor-mode-map
401 402 403 404
  (let ((map (make-sparse-keymap)))
    ;; Toggle between text and image display or editing
    (define-key map (kbd "C-c C-c") 'doc-view-toggle-display)
    map)
405
  "Keymap used by `doc-minor-view-mode'.")
Thien-Thi Nguyen's avatar
Thien-Thi Nguyen committed
406 407 408

;;;; Navigation Commands

409 410
(defmacro doc-view-current-page (&optional win)
  `(image-mode-window-get 'page ,win))
411 412 413 414 415
(defmacro doc-view-current-info () `(image-mode-window-get 'info))
(defmacro doc-view-current-overlay () `(image-mode-window-get 'overlay))
(defmacro doc-view-current-image () `(image-mode-window-get 'image))
(defmacro doc-view-current-slice () `(image-mode-window-get 'slice))

416 417 418
(defun doc-view-last-page-number ()
  (length doc-view-current-files))

Thien-Thi Nguyen's avatar
Thien-Thi Nguyen committed
419 420 421
(defun doc-view-goto-page (page)
  "View the page given by PAGE."
  (interactive "nPage: ")
422
  (let ((len (doc-view-last-page-number))
423
	(hscroll (window-hscroll)))
Thien-Thi Nguyen's avatar
Thien-Thi Nguyen committed
424
    (if (< page 1)
425
	(setq page 1)
426 427 428
      (when (and (> page len)
                 ;; As long as the converter is running, we don't know
                 ;; how many pages will be available.
429
                 (null doc-view-current-converter-processes))
430
	(setq page len)))
431 432
    (setf (doc-view-current-page) page
	  (doc-view-current-info)
433 434
	  (concat
	   (propertize
435
	    (format "Page %d of %d." page len) 'face 'bold)
436
	   ;; Tell user if converting isn't finished yet
437
	   (if doc-view-current-converter-processes
438 439 440 441
	       " (still converting...)\n"
	     "\n")
	   ;; Display context infos if this page matches the last search
	   (when (and doc-view-current-search-matches
442
		      (assq page doc-view-current-search-matches))
443 444
	     (concat (propertize "Search matches:\n" 'face 'bold)
		     (let ((contexts ""))
445
		       (dolist (m (cdr (assq page
446 447 448
					     doc-view-current-search-matches)))
			 (setq contexts (concat contexts "  - \"" m "\"\n")))
		       contexts)))))
Thien-Thi Nguyen's avatar
Thien-Thi Nguyen committed
449
    ;; Update the buffer
450 451 452
    ;; We used to find the file name from doc-view-current-files but
    ;; that's not right if the pages are not generated sequentially
    ;; or if the page isn't in doc-view-current-files yet.
453 454 455
    (let ((file (expand-file-name (format "page-%d.png" page)
                                  (doc-view-current-cache-dir))))
      (doc-view-insert-image file :pointer 'arrow)
456
      (set-window-hscroll (selected-window) hscroll)
457 458 459 460
      (when (and (not (file-exists-p file))
                 doc-view-current-converter-processes)
        ;; The PNG file hasn't been generated yet.
        (doc-view-pdf->png-1 doc-view-buffer-file-name file page
461
                             (let ((win (selected-window)))
462 463 464 465 466
                               (lambda ()
                                 (and (eq (current-buffer) (window-buffer win))
                                      ;; If we changed page in the mean
                                      ;; time, don't mess things up.
                                      (eq (doc-view-current-page win) page)
467 468
                                      ;; Make sure we don't infloop.
                                      (file-readable-p file)
469
                                      (with-selected-window win
470
							    (doc-view-goto-page page))))))))
471 472
    (overlay-put (doc-view-current-overlay)
                 'help-echo (doc-view-current-info))))
Thien-Thi Nguyen's avatar
Thien-Thi Nguyen committed
473 474 475 476

(defun doc-view-next-page (&optional arg)
  "Browse ARG pages forward."
  (interactive "p")
477
  (doc-view-goto-page (+ (doc-view-current-page) (or arg 1))))
Thien-Thi Nguyen's avatar
Thien-Thi Nguyen committed
478 479 480 481

(defun doc-view-previous-page (&optional arg)
  "Browse ARG pages backward."
  (interactive "p")
482
  (doc-view-goto-page (- (doc-view-current-page) (or arg 1))))
Thien-Thi Nguyen's avatar
Thien-Thi Nguyen committed
483 484 485 486 487 488 489 490 491

(defun doc-view-first-page ()
  "View the first page."
  (interactive)
  (doc-view-goto-page 1))

(defun doc-view-last-page ()
  "View the last page."
  (interactive)
492
  (doc-view-goto-page (doc-view-last-page-number)))
Thien-Thi Nguyen's avatar
Thien-Thi Nguyen committed
493

494 495
(defun doc-view-scroll-up-or-next-page (&optional arg)
  "Scroll page up ARG lines if possible, else goto next page.
Juri Linkov's avatar
Juri Linkov committed
496
When `doc-view-continuous' is non-nil, scrolling upward
497 498 499
at the bottom edge of the page moves to the next page.
Otherwise, goto next page only on typing SPC (ARG is nil)."
  (interactive "P")
Juri Linkov's avatar
Juri Linkov committed
500
  (if (or doc-view-continuous (null arg))
501 502 503 504 505 506 507 508 509 510 511 512
      (let ((hscroll (window-hscroll))
	    (cur-page (doc-view-current-page)))
	(when (= (window-vscroll) (image-scroll-up arg))
	  (doc-view-next-page)
	  (when (/= cur-page (doc-view-current-page))
	    (image-bob)
	    (image-bol 1))
	  (set-window-hscroll (selected-window) hscroll)))
    (image-scroll-up arg)))

(defun doc-view-scroll-down-or-previous-page (&optional arg)
  "Scroll page down ARG lines if possible, else goto previous page.
Juri Linkov's avatar
Juri Linkov committed
513
When `doc-view-continuous' is non-nil, scrolling downward
514 515 516
at the top edge of the page moves to the previous page.
Otherwise, goto previous page only on typing DEL (ARG is nil)."
  (interactive "P")
Juri Linkov's avatar
Juri Linkov committed
517
  (if (or doc-view-continuous (null arg))
518 519 520 521 522 523 524 525 526 527 528 529
      (let ((hscroll (window-hscroll))
	    (cur-page (doc-view-current-page)))
	(when (= (window-vscroll) (image-scroll-down arg))
	  (doc-view-previous-page)
	  (when (/= cur-page (doc-view-current-page))
	    (image-eob)
	    (image-bol 1))
	  (set-window-hscroll (selected-window) hscroll)))
    (image-scroll-down arg)))

(defun doc-view-next-line-or-next-page (&optional arg)
  "Scroll upward by ARG lines if possible, else goto next page.
Juri Linkov's avatar
Juri Linkov committed
530
When `doc-view-continuous' is non-nil, scrolling a line upward
531
at the bottom edge of the page moves to the next page."
532
  (interactive "p")
Juri Linkov's avatar
Juri Linkov committed
533
  (if doc-view-continuous
534 535
      (let ((hscroll (window-hscroll))
	    (cur-page (doc-view-current-page)))
536
	(when (= (window-vscroll) (image-next-line arg))
537 538 539 540 541 542 543
	  (doc-view-next-page)
	  (when (/= cur-page (doc-view-current-page))
	    (image-bob)
	    (image-bol 1))
	  (set-window-hscroll (selected-window) hscroll)))
    (image-next-line 1)))

544 545
(defun doc-view-previous-line-or-previous-page (&optional arg)
  "Scroll downward by ARG lines if possible, else goto previous page.
Juri Linkov's avatar
Juri Linkov committed
546
When `doc-view-continuous' is non-nil, scrolling a line downward
547 548
at the top edge of the page moves to the previous page."
  (interactive "p")
Juri Linkov's avatar
Juri Linkov committed
549
  (if doc-view-continuous
550 551
      (let ((hscroll (window-hscroll))
	    (cur-page (doc-view-current-page)))
552
	(when (= (window-vscroll) (image-previous-line arg))
553 554 555 556 557
	  (doc-view-previous-page)
	  (when (/= cur-page (doc-view-current-page))
	    (image-eob)
	    (image-bol 1))
	  (set-window-hscroll (selected-window) hscroll)))
558
    (image-previous-line arg)))
559

560 561
;;;; Utility Functions

562
(defun doc-view-kill-proc ()
563
  "Kill the current converter process(es)."
564
  (interactive)
565
  (while (consp doc-view-current-converter-processes)
566 567
    (ignore-errors ;; Some entries might not be processes, and maybe
		   ;; some are dead already?
568
      (kill-process (pop doc-view-current-converter-processes))))
569 570 571 572 573
  (when doc-view-current-timer
    (cancel-timer doc-view-current-timer)
    (setq doc-view-current-timer nil))
  (setq mode-line-process nil))

Thien-Thi Nguyen's avatar
Thien-Thi Nguyen committed
574 575 576
(defun doc-view-kill-proc-and-buffer ()
  "Kill the current converter process and buffer."
  (interactive)
577
  (doc-view-kill-proc)
Thien-Thi Nguyen's avatar
Thien-Thi Nguyen committed
578 579 580
  (when (eq major-mode 'doc-view-mode)
    (kill-buffer (current-buffer))))

581 582 583
(defun doc-view-make-safe-dir (dir)
  (condition-case nil
      (let ((umask (default-file-modes)))
584 585 586 587 588 589 590 591 592
	(unwind-protect
	    (progn
	      ;; Create temp files with strict access rights.  It's easy to
	      ;; loosen them later, whereas it's impossible to close the
	      ;; time-window of loose permissions otherwise.
	      (set-default-file-modes #o0700)
	      (make-directory dir))
	  ;; Reset the umask.
	  (set-default-file-modes umask)))
593
    (file-already-exists
594 595
     (when (file-symlink-p dir)
       (error "Danger: %s points to a symbolic link" dir))
596 597 598 599 600 601 602 603
     ;; In case it was created earlier with looser rights.
     ;; We could check the mode info returned by file-attributes, but it's
     ;; a pain to parse and it may not tell you what we want under
     ;; non-standard file-systems.  So let's just say what we want and let
     ;; the underlying C code and file-system figure it out.
     ;; This also ends up checking a bunch of useful conditions: it makes
     ;; sure we have write-access to the directory and that we own it, thus
     ;; closing a bunch of security holes.
604 605 606 607 608 609
     (condition-case error
	 (set-file-modes dir #o0700)
       (file-error
	(error
	 (format "Unable to use temporary directory %s: %s"
		 dir (mapconcat 'identity (cdr error) " "))))))))
610

611 612 613 614 615
(defun doc-view-current-cache-dir ()
  "Return the directory where the png files of the current doc should be saved.
It's a subdirectory of `doc-view-cache-directory'."
  (if doc-view-current-cache-dir
      doc-view-current-cache-dir
616 617 618
    ;; Try and make sure doc-view-cache-directory exists and is safe.
    (doc-view-make-safe-dir doc-view-cache-directory)
    ;; Now compute the subdirectory to use.
619 620
    (setq doc-view-current-cache-dir
	  (file-name-as-directory
621
	   (expand-file-name
622
	    (concat (file-name-nondirectory doc-view-buffer-file-name)
623 624 625
		    "-"
		    (let ((file doc-view-buffer-file-name))
		      (with-temp-buffer
626
			(set-buffer-multibyte nil)
627 628
			(insert-file-contents-literally file)
			(md5 (current-buffer)))))
629
            doc-view-cache-directory)))))
630 631 632 633

(defun doc-view-remove-if (predicate list)
  "Return LIST with all items removed that satisfy PREDICATE."
  (let (new-list)
634
    (dolist (item list)
635
      (when (not (funcall predicate item))
636 637
	(setq new-list (cons item new-list))))
     (nreverse new-list)))
638

639 640
;;;###autoload
(defun doc-view-mode-p (type)
641 642 643
  "Return non-nil if document type TYPE is available for `doc-view'.
Document types are symbols like `dvi', `ps', `pdf', or `odf' (any
OpenDocument format)."
644
  (and (display-graphic-p)
645 646
       (or (image-type-available-p 'imagemagick)
	   (image-type-available-p 'png))
647 648 649
       (cond
	((eq type 'dvi)
	 (and (doc-view-mode-p 'pdf)
650 651 652 653
	      (or (and doc-view-dvipdf-program
		       (executable-find doc-view-dvipdf-program))
		  (and doc-view-dvipdfm-program
		       (executable-find doc-view-dvipdfm-program)))))
654
	((or (eq type 'postscript) (eq type 'ps) (eq type 'eps)
655 656 657
	     (eq type 'pdf))
	 (and doc-view-ghostscript-program
	      (executable-find doc-view-ghostscript-program)))
658 659 660 661
	((eq type 'odf)
	 (and doc-view-unoconv-program
	      (executable-find doc-view-unoconv-program)
	      (doc-view-mode-p 'pdf)))
662 663 664
	(t ;; unknown image type
	 nil))))

Thien-Thi Nguyen's avatar
Thien-Thi Nguyen committed
665 666
;;;; Conversion Functions

667 668 669
(defvar doc-view-shrink-factor 1.125)

(defun doc-view-enlarge (factor)
670
  "Enlarge the document by FACTOR."
671
  (interactive (list doc-view-shrink-factor))
672 673
  (if (eq (plist-get (cdr (doc-view-current-image)) :type)
	  'imagemagick)
674 675 676 677 678 679 680 681 682 683 684
      ;; ImageMagick supports on-the-fly-rescaling.
      (let ((new (ceiling (* factor doc-view-image-width))))
        (unless (equal new doc-view-image-width)
          (set (make-local-variable 'doc-view-image-width) new)
          (doc-view-insert-image
           (plist-get (cdr (doc-view-current-image)) :file)
           :width doc-view-image-width)))
    (let ((new (ceiling (* factor doc-view-resolution))))
      (unless (equal new doc-view-resolution)
        (set (make-local-variable 'doc-view-resolution) new)
        (doc-view-reconvert-doc)))))
685 686 687 688 689 690

(defun doc-view-shrink (factor)
  "Shrink the document."
  (interactive (list doc-view-shrink-factor))
  (doc-view-enlarge (/ 1.0 factor)))

691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751
(defun doc-view-fit-width-to-window ()
  "Fit the image width to the window width."
  (interactive)
  (let ((win-width (- (nth 2 (window-inside-pixel-edges))
                      (nth 0 (window-inside-pixel-edges))))
        (slice (doc-view-current-slice)))
    (if (not slice)
        (let ((img-width (car (image-display-size
                               (image-get-display-property) t))))
          (doc-view-enlarge (/ (float win-width) (float img-width))))

      ;; If slice is set
      (let* ((slice-width (nth 2 slice))
             (scale-factor (/ (float win-width) (float slice-width)))
             (new-slice (mapcar (lambda (x) (ceiling (* scale-factor x))) slice)))

        (doc-view-enlarge scale-factor)
        (setf (doc-view-current-slice) new-slice)
        (doc-view-goto-page (doc-view-current-page))))))

(defun doc-view-fit-height-to-window ()
  "Fit the image height to the window height."
  (interactive)
  (let ((win-height (- (nth 3 (window-inside-pixel-edges))
                       (nth 1 (window-inside-pixel-edges))))
        (slice (doc-view-current-slice)))
    (if (not slice)
        (let ((img-height (cdr (image-display-size
                                (image-get-display-property) t))))
          ;; When users call 'doc-view-fit-height-to-window',
          ;; they might want to go to next page by typing SPC
          ;; ONLY once. So I used '(- win-height 1)' instead of
          ;; 'win-height'
          (doc-view-enlarge (/ (float (- win-height 1)) (float img-height))))

      ;; If slice is set
      (let* ((slice-height (nth 3 slice))
             (scale-factor (/ (float (- win-height 1)) (float slice-height)))
             (new-slice (mapcar (lambda (x) (ceiling (* scale-factor x))) slice)))

        (doc-view-enlarge scale-factor)
        (setf (doc-view-current-slice) new-slice)
        (doc-view-goto-page (doc-view-current-page))))))

(defun doc-view-fit-page-to-window ()
  "Fit the image to the window.
More specifically, this function enlarges image by:

min {(window-width / image-width), (window-height / image-height)} times."
  (interactive)
  (let ((win-width (- (nth 2 (window-inside-pixel-edges))
                      (nth 0 (window-inside-pixel-edges))))
        (win-height (- (nth 3 (window-inside-pixel-edges))
                       (nth 1 (window-inside-pixel-edges))))
        (slice (doc-view-current-slice)))
    (if (not slice)
        (let ((img-width (car (image-display-size
                               (image-get-display-property) t)))
              (img-height (cdr (image-display-size
                                (image-get-display-property) t))))
          (doc-view-enlarge (min (/ (float win-width) (float img-width))
752 753
                                 (/ (float (- win-height 1))
                                    (float img-height)))))
754 755 756 757
      ;; If slice is set
      (let* ((slice-width (nth 2 slice))
             (slice-height (nth 3 slice))
             (scale-factor (min (/ (float win-width) (float slice-width))
758 759
                                (/ (float (- win-height 1))
                                   (float slice-height))))
760 761 762 763 764
             (new-slice (mapcar (lambda (x) (ceiling (* scale-factor x))) slice)))
        (doc-view-enlarge scale-factor)
        (setf (doc-view-current-slice) new-slice)
        (doc-view-goto-page (doc-view-current-page))))))

765
(defun doc-view-reconvert-doc ()
766 767 768
  "Reconvert the current document.
Should be invoked when the cached images aren't up-to-date."
  (interactive)
769 770 771
  (doc-view-kill-proc)
  ;; Clear the old cached files
  (when (file-exists-p (doc-view-current-cache-dir))
772
    (delete-directory (doc-view-current-cache-dir) 'recursive))
773
  (kill-local-variable 'doc-view-last-page-number)
774
  (doc-view-initiate-display))
Thien-Thi Nguyen's avatar
Thien-Thi Nguyen committed
775

776 777
(defun doc-view-sentinel (proc event)
  "Generic sentinel for doc-view conversion processes."
Thien-Thi Nguyen's avatar
Thien-Thi Nguyen committed
778
  (if (not (string-match "finished" event))
779
      (message "DocView: process %s changed status to %s."
780 781 782 783
               (process-name proc)
	       (if (string-match "\\(.+\\)\n?\\'" event)
		   (match-string 1 event)
		 event))
784 785 786 787 788 789 790 791 792 793
    (when (buffer-live-p (process-get proc 'buffer))
      (with-current-buffer (process-get proc 'buffer)
        (setq doc-view-current-converter-processes
              (delq proc doc-view-current-converter-processes))
        (setq mode-line-process
              (if doc-view-current-converter-processes
                  (format ":%s" (car doc-view-current-converter-processes))))
        (funcall (process-get proc 'callback))))))

(defun doc-view-start-process (name program args callback)
794 795 796 797 798
  ;; Make sure the process is started in an existing directory, (rather than
  ;; some file-name-handler-managed dir, for example).
  (let* ((default-directory (if (file-readable-p default-directory)
				default-directory
			      (expand-file-name "~/")))
799 800 801 802 803 804 805
         (proc (apply 'start-process name doc-view-conversion-buffer
                      program args)))
    (push proc doc-view-current-converter-processes)
    (setq mode-line-process (list (format ":%s" proc)))
    (set-process-sentinel proc 'doc-view-sentinel)
    (process-put proc 'buffer   (current-buffer))
    (process-put proc 'callback callback)))
806 807 808

(defun doc-view-dvi->pdf (dvi pdf callback)
  "Convert DVI to PDF asynchronously and call CALLBACK when finished."
809 810 811 812 813
  ;; Prefer dvipdf over dvipdfm, because the latter has problems if the DVI
  ;; references and includes other PS files.
  (if (and doc-view-dvipdf-program
	   (executable-find doc-view-dvipdf-program))
      (doc-view-start-process "dvi->pdf" doc-view-dvipdf-program
814 815
			      (list dvi pdf)
			      callback)
816 817 818
    (doc-view-start-process "dvi->pdf" doc-view-dvipdfm-program
			    (list "-o" pdf dvi)
			    callback)))
819

820 821 822 823 824 825 826
(defun doc-view-odf->pdf (odf callback)
  "Convert ODF to PDF asynchronously and call CALLBACK when finished.
The converted PDF is put into the current cache directory, and it
is named like ODF with the extension turned to pdf."
  (doc-view-start-process "odf->pdf" doc-view-unoconv-program
			  (list "-f" "pdf" "-o" (doc-view-current-cache-dir) odf)
			  callback))
Thien-Thi Nguyen's avatar
Thien-Thi Nguyen committed
827 828

(defun doc-view-pdf/ps->png (pdf-ps png)
829
  "Convert PDF-PS to PNG asynchronously."
830 831 832 833 834 835
  (doc-view-start-process
   "pdf/ps->png" doc-view-ghostscript-program
   (append doc-view-ghostscript-options
           (list (format "-r%d" (round doc-view-resolution))
                 (concat "-sOutputFile=" png)
                 pdf-ps))
836
   (let ((resolution doc-view-resolution))
837 838 839 840 841 842 843 844 845 846 847
     (lambda ()
       ;; Only create the resolution file when it's all done, so it also
       ;; serves as a witness that the conversion is complete.
       (write-region (prin1-to-string resolution) nil
                     (expand-file-name "resolution.el"
                                       (doc-view-current-cache-dir))
                     nil 'silently)
       (when doc-view-current-timer
         (cancel-timer doc-view-current-timer)
         (setq doc-view-current-timer nil))
       (doc-view-display (current-buffer) 'force))))
848
  ;; Update the displayed pages as soon as they're done generating.
Thien-Thi Nguyen's avatar
Thien-Thi Nguyen committed
849 850
  (when doc-view-conversion-refresh-interval
    (setq doc-view-current-timer
851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869
          (run-at-time "1 secs" doc-view-conversion-refresh-interval
                       'doc-view-display
                       (current-buffer)))))

(defun doc-view-pdf->png-1 (pdf png page callback)
  "Convert a PAGE of a PDF file to PNG asynchronously.
Call CALLBACK with no arguments when done."
  (doc-view-start-process
   "pdf->png-1" doc-view-ghostscript-program
   (append doc-view-ghostscript-options
           (list (format "-r%d" (round doc-view-resolution))
                 ;; Sadly, `gs' only supports the page-range
                 ;; for PDF files.
                 (format "-dFirstPage=%d" page)
                 (format "-dLastPage=%d" page)
                 (concat "-sOutputFile=" png)
                 pdf))
   callback))

870 871
(declare-function clear-image-cache "image.c" (&optional filter))

872 873 874 875 876 877 878 879 880
(defun doc-view-pdf->png (pdf png pages)
  "Convert a PDF file to PNG asynchronously.
Start by converting PAGES, and then the rest."
  (if (null pages)
      (doc-view-pdf/ps->png pdf png)
    ;; We could render several `pages' with a single process if they're
    ;; (almost) consecutive, but since in 99% of the cases, there'll be only
    ;; a single page anyway, and of the remaining 1%, few cases will have
    ;; consecutive pages, it's not worth the trouble.
881
    (let ((rest (cdr pages)))
882 883 884 885 886 887 888
      (doc-view-pdf->png-1
       pdf (format png (car pages)) (car pages)
       (lambda ()
         (if rest
             (doc-view-pdf->png pdf png rest)
           ;; Yippie, the important pages are done, update the display.
           (clear-image-cache)
889 890 891 892 893
           ;; For the windows that have a message (like "Welcome to
           ;; DocView") display property, clearing the image cache is
           ;; not sufficient.
           (dolist (win (get-buffer-window-list (current-buffer) nil 'visible))
             (with-selected-window win
894 895
				   (when (stringp (get-char-property (point-min) 'display))
				     (doc-view-goto-page (doc-view-current-page)))))
896 897
           ;; Convert the rest of the pages.
           (doc-view-pdf/ps->png pdf png)))))))
Thien-Thi Nguyen's avatar
Thien-Thi Nguyen committed
898

899 900
(defun doc-view-pdf->txt (pdf txt callback)
  "Convert PDF to TXT asynchronously and call CALLBACK when finished."
901
  (or (executable-find doc-view-pdftotext-program)
902
      (error "You need the `pdftotext' program to convert a PDF to text"))
903 904 905
  (doc-view-start-process "pdf->txt" doc-view-pdftotext-program
                          (list "-raw" pdf txt)
                          callback))
906

907 908
(defun doc-view-doc->txt (txt callback)
  "Convert the current document to text and call CALLBACK when done."
909
  (make-directory (doc-view-current-cache-dir) t)
Stefan Monnier's avatar
Stefan Monnier committed
910 911
  (pcase doc-view-doc-type
    (`pdf
912 913
     ;; Doc is a PDF, so convert it to TXT
     (doc-view-pdf->txt doc-view-buffer-file-name txt callback))
Stefan Monnier's avatar
Stefan Monnier committed
914
    (`ps
915 916
     ;; Doc is a PS, so convert it to PDF (which will be converted to
     ;; TXT thereafter).
917 918
     (let ((pdf (expand-file-name "doc.pdf"
				  (doc-view-current-cache-dir))))
919 920
       (doc-view-ps->pdf doc-view-buffer-file-name pdf
                         (lambda () (doc-view-pdf->txt pdf txt callback)))))
Stefan Monnier's avatar
Stefan Monnier committed
921
    (`dvi
922 923 924 925 926
     ;; Doc is a DVI.  This means that a doc.pdf already exists in its
     ;; cache subdirectory.
     (doc-view-pdf->txt (expand-file-name "doc.pdf"
                                          (doc-view-current-cache-dir))
                        txt callback))
Stefan Monnier's avatar
Stefan Monnier committed
927
    (`odf
928 929 930 931 932
     ;; Doc is some ODF (or MS Office) doc.  This means that a doc.pdf
     ;; already exists in its cache subdirectory.
     (doc-view-pdf->txt (expand-file-name "doc.pdf"
                                          (doc-view-current-cache-dir))
                        txt callback))
Stefan Monnier's avatar
Stefan Monnier committed
933
    (_ (error "DocView doesn't know what to do"))))
934 935 936

(defun doc-view-ps->pdf (ps pdf callback)
  "Convert PS to PDF asynchronously and call CALLBACK when finished."
937
  (or (executable-find doc-view-ps2pdf-program)
938
      (error "You need the `ps2pdf' program to convert PS to PDF"))
939 940 941 942 943 944 945 946 947 948 949 950 951 952 953
  (doc-view-start-process "ps->pdf" doc-view-ps2pdf-program
                          (list
                           ;; Avoid security problems when rendering files from
                           ;; untrusted sources.
                           "-dSAFER"
                           ;; in-file and out-file
                           ps pdf)
                          callback))

(defun doc-view-active-pages ()
  (let ((pages ()))
    (dolist (win (get-buffer-window-list (current-buffer) nil 'visible))
      (let ((page (image-mode-window-get 'page win)))
        (unless (memq page pages) (push page pages))))
    pages))
Thien-Thi Nguyen's avatar
Thien-Thi Nguyen committed
954

955
(defun doc-view-convert-current-doc ()
956
  "Convert `doc-view-buffer-file-name' to a set of png files, one file per page.
957 958
Those files are saved in the directory given by the function
`doc-view-current-cache-dir'."
959 960 961 962 963 964 965 966
  ;; Let stale files still display while we recompute the new ones, so only
  ;; flush the cache when the conversion is over.  One of the reasons why it
  ;; is important to keep displaying the stale page is so that revert-buffer
  ;; preserves the horizontal/vertical scroll settings (which are otherwise
  ;; resets during the redisplay).
  (setq doc-view-pending-cache-flush t)
  (let ((png-file (expand-file-name "page-%d.png"
                                    (doc-view-current-cache-dir))))
967
    (make-directory (doc-view-current-cache-dir) t)
Stefan Monnier's avatar
Stefan Monnier committed
968 969
    (pcase doc-view-doc-type
      (`dvi
970 971
       ;; DVI files have to be converted to PDF before Ghostscript can process
       ;; it.
972
       (let ((pdf (expand-file-name "doc.pdf" doc-view-current-cache-dir)))
973 974
         (doc-view-dvi->pdf doc-view-buffer-file-name pdf
                            (lambda () (doc-view-pdf/ps->png pdf png-file)))))
Stefan Monnier's avatar
Stefan Monnier committed
975
      (`odf
976 977
       ;; ODF files have to be converted to PDF before Ghostscript can
       ;; process it.
978
       (let ((pdf (expand-file-name "doc.pdf" doc-view-current-cache-dir))