84422
|
1 ;;; doc-view.el --- View PDF/PostScript/DVI files in Emacs
|
|
2
|
|
3 ;; Copyright (C) 2007 Free Software Foundation, Inc.
|
|
4 ;;
|
|
5 ;; Author: Tassilo Horn <tassilo@member.fsf.org>
|
|
6 ;; Maintainer: Tassilo Horn <tassilo@member.fsf.org>
|
|
7 ;; Keywords: files, pdf, ps, dvi
|
|
8 ;; Version: <2007-09-07 Fri 15:28>
|
|
9
|
|
10 ;; This file is part of GNU Emacs.
|
|
11
|
|
12 ;; GNU Emacs is free software; you can redistribute it and/or modify
|
|
13 ;; it under the terms of the GNU General Public License as published by
|
|
14 ;; the Free Software Foundation; either version 3, or (at your option)
|
|
15 ;; any later version.
|
|
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
|
|
23 ;; along with GNU Emacs; see the file COPYING. If not, write to the
|
|
24 ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
|
25 ;; Boston, MA 02110-1301, USA.
|
|
26
|
|
27 ;;; Requirements:
|
|
28
|
|
29 ;; doc-view.el requires GNU Emacs 22.1 or newer. You also need GhostScript,
|
|
30 ;; `dvipdfm' which comes with TeTeX and `pdftotext', which comes with poppler
|
|
31 ;; (http://poppler.freedesktop.org/).
|
|
32
|
|
33 ;;; Commentary:
|
|
34
|
|
35 ;; DocView is a document viewer for Emacs. It converts PDF, PS and DVI files
|
|
36 ;; to a set of PNG files, one PNG for each page, and displays the PNG images
|
|
37 ;; inside an Emacs buffer. This buffer uses `doc-view-mode' which provides
|
|
38 ;; convenient key bindings for browsing the document.
|
|
39 ;;
|
|
40 ;; To use it simply do
|
|
41 ;;
|
|
42 ;; M-x doc-view RET
|
|
43 ;;
|
|
44 ;; and you'll be queried for a document to open.
|
|
45 ;;
|
|
46 ;; Since conversion may take some time all the PNG images are cached in a
|
|
47 ;; subdirectory of `doc-view-cache-directory' and reused when you want to view
|
|
48 ;; that file again. This reusing can be omitted if you provide a prefx
|
|
49 ;; argument to `doc-view'. To delete all cached files use
|
|
50 ;; `doc-view-clear-cache'. To open the cache with dired, so that you can tidy
|
|
51 ;; it out use `doc-view-dired-cache'.
|
|
52 ;;
|
|
53 ;; When conversion in underway the first page will be displayed as soon as it
|
|
54 ;; is available and the available pages are refreshed every
|
|
55 ;; `doc-view-conversion-refresh-interval' seconds. If that variable is nil the
|
|
56 ;; pages won't be displayed before conversion of the document finished
|
|
57 ;; completely.
|
|
58 ;;
|
|
59 ;; DocView lets you select a slice of the displayed pages. This slice will be
|
|
60 ;; remembered and applied to all pages of the current document. This enables
|
|
61 ;; you to cut away the margins of a document to save some space. To select a
|
|
62 ;; slice you can use `doc-view-set-slice' (bound to `s s') which will query you
|
|
63 ;; for the coordinates of the slice's top-left corner and its width and height.
|
|
64 ;; A much more convenient way to do the same is offered by the command
|
|
65 ;; `doc-view-set-slice-using-mouse' (bound to `s m'). After invokation you
|
|
66 ;; only have to press mouse-1 at the top-left corner and drag it to the
|
|
67 ;; bottom-right corner of the desired slice. To reset the slice use
|
|
68 ;; `doc-view-reset-slice' (bound to `s r').
|
|
69 ;;
|
|
70 ;; Dired users should have a look at `doc-view-dired'.
|
|
71 ;;
|
|
72 ;; You can also search within the document. The command `doc-view-search'
|
|
73 ;; (bound to `C-s') queries for a search regexp and initializes a list of all
|
|
74 ;; matching pages and messages how many match-pages were found. After that you
|
|
75 ;; can jump to the next page containing a match with
|
|
76 ;; `doc-view-search-next-match' (bound to `C-S-n') or to the previous matching
|
|
77 ;; page with `doc-view-search-previous-match' (bound to `C-S-p'). This works
|
|
78 ;; by searching a plain text representation of the document. If that doesn't
|
|
79 ;; already exist the first invokation of `doc-view-search' starts the
|
|
80 ;; conversion. When that finishes and you're still viewing the document
|
|
81 ;; (i.e. you didn't switch to another buffer) you're queried for the regexp
|
|
82 ;; then.
|
|
83
|
|
84 ;;; Configuration:
|
|
85
|
|
86 ;; Basically doc-view should be quite usable with its standard settings, so
|
|
87 ;; putting
|
|
88 ;;
|
|
89 ;; (require 'doc-view)
|
|
90 ;;
|
|
91 ;; into your `user-init-file' should be enough. If the images are too small or
|
|
92 ;; too big you should set the "-rXXX" option in `doc-view-ghostscript-options'
|
|
93 ;; to another value. (The bigger your screen, the higher the value.)
|
|
94 ;;
|
|
95 ;; This and all other options can be set with the customization interface.
|
|
96 ;; Simply do
|
|
97 ;;
|
|
98 ;; M-x customize-group RET doc-view RET
|
|
99 ;;
|
|
100 ;; and modify them to your needs.
|
|
101
|
|
102 ;;; Code:
|
|
103
|
|
104 (require 'dired)
|
|
105 (eval-when-compile (require 'cl))
|
|
106
|
|
107 ;;;; Customization Options
|
|
108
|
|
109 (defgroup doc-view nil
|
|
110 "In-buffer viewer for PDF, PostScript and DVI files."
|
|
111 :link '(function-link doc-view)
|
|
112 :version "22.2"
|
|
113 :group 'applications
|
|
114 :group 'multimedia
|
|
115 :prefix "doc-view-")
|
|
116
|
|
117 (defcustom doc-view-ghostscript-program "gs"
|
|
118 "Program to convert PS and PDF files to PNG."
|
|
119 :type '(file)
|
|
120 :group 'doc-view)
|
|
121
|
|
122 (defcustom doc-view-ghostscript-options
|
|
123 '("-dNOPAUSE" "-sDEVICE=png16m" "-dTextAlphaBits=4"
|
|
124 "-dBATCH" "-dGraphicsAlphaBits=4" "-dQUIET"
|
|
125 "-r100")
|
|
126 "A list of options to give to ghostview."
|
|
127 :type '(sexp)
|
|
128 :group 'doc-view)
|
|
129
|
|
130 (defcustom doc-view-dvipdfm-program "dvipdfm"
|
|
131 "Program to convert DVI files to PDF.
|
|
132
|
|
133 DVI file will be converted to PDF before the resulting PDF is
|
|
134 converted to PNG."
|
|
135 :type '(file)
|
|
136 :group 'doc-view)
|
|
137
|
|
138 (defcustom doc-view-ps2pdf-program "ps2pdf"
|
|
139 "Program to convert PS files to PDF.
|
|
140
|
|
141 PS files will be converted to PDF before searching is possible."
|
|
142 :type '(file)
|
|
143 :group 'doc-view)
|
|
144
|
|
145 (defcustom doc-view-pdftotext-program "pdftotext"
|
|
146 "Program to convert PDF files to plain text.
|
|
147
|
|
148 Needed for searching."
|
|
149 :type '(file)
|
|
150 :group 'doc-view)
|
|
151
|
|
152 (defcustom doc-view-cache-directory (concat temporary-file-directory
|
|
153 "doc-view")
|
|
154 "The base directory, where the PNG images will be saved."
|
|
155 :type '(directory)
|
|
156 :group 'doc-view)
|
|
157
|
|
158 (defcustom doc-view-conversion-buffer "*doc-view conversion output*"
|
|
159 "The buffer where messages from the converter programs go to."
|
|
160 :type '(string)
|
|
161 :group 'doc-view)
|
|
162
|
|
163 (defcustom doc-view-conversion-refresh-interval 3
|
|
164 "Every how much seconds the DocView buffer gets refreshed while conversion.
|
|
165 After such an refresh newly converted pages will be available for
|
|
166 viewing. If set to nil there won't be any refreshes and the
|
|
167 pages won't be displayed before conversion of the whole document
|
|
168 has finished."
|
|
169 :type '(string)
|
|
170 :group 'doc-view)
|
|
171
|
|
172 ;;;; Internal Variables
|
|
173
|
|
174 (defvar doc-view-current-files nil
|
|
175 "Only used internally.")
|
|
176
|
|
177 (defvar doc-view-current-page nil
|
|
178 "Only used internally.")
|
|
179
|
|
180 (defvar doc-view-current-doc nil
|
|
181 "Only used internally.")
|
|
182
|
|
183 (defvar doc-view-current-converter-process nil
|
|
184 "Only used internally.")
|
|
185
|
|
186 (defvar doc-view-current-timer nil
|
|
187 "Only used internally.")
|
|
188
|
|
189 (defvar doc-view-current-slice nil
|
|
190 "Only used internally.")
|
|
191
|
|
192 (defvar doc-view-current-cache-dir nil
|
|
193 "Only used internally.")
|
|
194
|
|
195 (defvar doc-view-current-search-matches nil
|
|
196 "Only used internally.")
|
|
197
|
|
198 (defvar doc-view-current-image nil
|
|
199 "Only used internally.")
|
|
200
|
|
201 (defvar doc-view-current-info nil
|
|
202 "Only used internally.")
|
|
203
|
|
204 ;;;; DocView Keymap
|
|
205
|
|
206 (defvar doc-view-mode-map
|
|
207 (let ((map (make-sparse-keymap)))
|
|
208 ;; Navigation in the document
|
|
209 (define-key map (kbd "n") 'doc-view-next-page)
|
|
210 (define-key map (kbd "p") 'doc-view-previous-page)
|
|
211 (define-key map (kbd "<next>") 'doc-view-next-page)
|
|
212 (define-key map (kbd "<prior>") 'doc-view-previous-page)
|
|
213 (define-key map (kbd "SPC") 'doc-view-scroll-up-or-next-page)
|
|
214 (define-key map (kbd "DEL") 'doc-view-scroll-down-or-previous-page)
|
|
215 (define-key map (kbd "M-<") 'doc-view-first-page)
|
|
216 (define-key map (kbd "M->") 'doc-view-last-page)
|
|
217 (define-key map (kbd "g") 'doc-view-goto-page)
|
|
218 ;; Killing/burying the buffer (and the process)
|
|
219 (define-key map (kbd "q") 'bury-buffer)
|
|
220 (define-key map (kbd "k") 'doc-view-kill-proc-and-buffer)
|
|
221 (define-key map (kbd "C-x k") 'doc-view-kill-proc-and-buffer)
|
|
222 ;; Slicing the image
|
|
223 (define-key map (kbd "s s") 'doc-view-set-slice)
|
|
224 (define-key map (kbd "s m") 'doc-view-set-slice-using-mouse)
|
|
225 (define-key map (kbd "s r") 'doc-view-reset-slice)
|
|
226 ;; Searching
|
|
227 (define-key map (kbd "C-s") 'doc-view-search)
|
|
228 (define-key map (kbd "<find>") 'doc-view-search)
|
|
229 (define-key map (kbd "C-S-n") 'doc-view-search-next-match)
|
|
230 (define-key map (kbd "C-S-p") 'doc-view-search-previous-match)
|
|
231 ;; Scrolling
|
|
232 (define-key map (kbd "C-v") 'scroll-up)
|
|
233 (define-key map (kbd "<mouse-4>") 'mwheel-scroll)
|
|
234 (define-key map (kbd "<mouse-5>") 'mwheel-scroll)
|
|
235 (define-key map (kbd "M-v") 'scroll-down)
|
|
236 ;; Show the tooltip
|
|
237 (define-key map (kbd "C-t") 'doc-view-show-tooltip)
|
|
238 (suppress-keymap map)
|
|
239 map)
|
|
240 "Keymap used by `doc-view-mode'.")
|
|
241
|
|
242 ;;;; Navigation Commands
|
|
243
|
|
244 (defun doc-view-goto-page (page)
|
|
245 "View the page given by PAGE."
|
|
246 (interactive "nPage: ")
|
|
247 (let ((len (length doc-view-current-files)))
|
|
248 (if (< page 1)
|
|
249 (setq page 1)
|
|
250 (when (> page len)
|
|
251 (setq page len)))
|
|
252 (setq doc-view-current-page page
|
|
253 doc-view-current-info
|
|
254 (concat
|
|
255 (propertize
|
|
256 (format "Page %d of %d."
|
|
257 doc-view-current-page
|
|
258 len) 'face 'bold)
|
|
259 ;; Tell user if converting isn't finished yet
|
|
260 (if doc-view-current-converter-process
|
|
261 " (still converting...)\n"
|
|
262 "\n")
|
|
263 ;; Display context infos if this page matches the last search
|
|
264 (when (and doc-view-current-search-matches
|
|
265 (assq doc-view-current-page
|
|
266 doc-view-current-search-matches))
|
|
267 (concat (propertize "Search matches:\n" 'face 'bold)
|
|
268 (let ((contexts ""))
|
|
269 (dolist (m (cdr (assq doc-view-current-page
|
|
270 doc-view-current-search-matches)))
|
|
271 (setq contexts (concat contexts " - \"" m "\"\n")))
|
|
272 contexts)))))
|
|
273 ;; Update the buffer
|
|
274 (setq inhibit-read-only t)
|
|
275 (erase-buffer)
|
|
276 (let ((beg (point)))
|
|
277 (doc-view-insert-image (nth (1- page) doc-view-current-files)
|
|
278 :pointer 'arrow)
|
|
279 (put-text-property beg (point) 'help-echo doc-view-current-info))
|
|
280 (insert "\n" doc-view-current-info)
|
|
281 (goto-char (point-min))
|
|
282 (forward-char)
|
|
283 (setq inhibit-read-only nil)))
|
|
284
|
|
285 (defun doc-view-next-page (&optional arg)
|
|
286 "Browse ARG pages forward."
|
|
287 (interactive "p")
|
|
288 (doc-view-goto-page (+ doc-view-current-page (or arg 1))))
|
|
289
|
|
290 (defun doc-view-previous-page (&optional arg)
|
|
291 "Browse ARG pages backward."
|
|
292 (interactive "p")
|
|
293 (doc-view-goto-page (- doc-view-current-page (or arg 1))))
|
|
294
|
|
295 (defun doc-view-first-page ()
|
|
296 "View the first page."
|
|
297 (interactive)
|
|
298 (doc-view-goto-page 1))
|
|
299
|
|
300 (defun doc-view-last-page ()
|
|
301 "View the last page."
|
|
302 (interactive)
|
|
303 (doc-view-goto-page (length doc-view-current-files)))
|
|
304
|
|
305 (defun doc-view-scroll-up-or-next-page ()
|
|
306 "Scroll page up if possible, else goto next page."
|
|
307 (interactive)
|
|
308 (condition-case nil
|
|
309 (scroll-up)
|
|
310 (error (doc-view-next-page))))
|
|
311
|
|
312 (defun doc-view-scroll-down-or-previous-page ()
|
|
313 "Scroll page down if possible, else goto previous page."
|
|
314 (interactive)
|
|
315 (condition-case nil
|
|
316 (scroll-down)
|
|
317 (error (doc-view-previous-page)
|
|
318 (goto-char (point-max)))))
|
|
319
|
|
320 (defun doc-view-kill-proc-and-buffer ()
|
|
321 "Kill the current converter process and buffer."
|
|
322 (interactive)
|
|
323 (when (eq major-mode 'doc-view-mode)
|
|
324 (when doc-view-current-converter-process
|
|
325 (kill-process doc-view-current-converter-process))
|
|
326 (when doc-view-current-timer
|
|
327 (cancel-timer doc-view-current-timer)
|
|
328 (setq doc-view-current-timer nil))
|
|
329 (kill-buffer (current-buffer))))
|
|
330
|
|
331 ;;;; Conversion Functions
|
|
332
|
|
333 (defun doc-view-file-name-to-directory-name (file)
|
|
334 "Return the directory where the png files of FILE should be saved.
|
|
335
|
|
336 It'a a subdirectory of `doc-view-cache-directory'."
|
|
337 (if doc-view-current-cache-dir
|
|
338 doc-view-current-cache-dir
|
|
339 (file-name-as-directory
|
|
340 (concat (file-name-as-directory doc-view-cache-directory)
|
|
341 (with-temp-buffer
|
|
342 (insert-file-contents-literally file)
|
|
343 (md5 (current-buffer)))))))
|
|
344
|
|
345 (defun doc-view-dvi->pdf-sentinel (proc event)
|
|
346 "If DVI->PDF conversion was successful, convert the PDF to PNG
|
|
347 now."
|
|
348 (if (not (string-match "finished" event))
|
|
349 (message "DocView: dvi->pdf process changed status to %s." event)
|
|
350 (set-buffer (process-get proc 'buffer))
|
|
351 (setq doc-view-current-converter-process nil)
|
|
352 (message "DocView: finished conversion from DVI to PDF!")
|
|
353 ;; Now go on converting this PDF to a set of PNG files.
|
|
354 (let* ((pdf (process-get proc 'pdf-file))
|
|
355 (png (concat (doc-view-file-name-to-directory-name
|
|
356 doc-view-current-doc)
|
|
357 "page-%d.png")))
|
|
358 (doc-view-pdf/ps->png pdf png))))
|
|
359
|
|
360 (defun doc-view-dvi->pdf (dvi pdf)
|
|
361 "Convert DVI to PDF asynchrounously."
|
|
362 (message "DocView: converting DVI to PDF now!")
|
|
363 (setq doc-view-current-converter-process
|
|
364 (start-process "doc-view-dvi->pdf" doc-view-conversion-buffer
|
|
365 doc-view-dvipdfm-program
|
|
366 "-o" pdf dvi))
|
|
367 (set-process-sentinel doc-view-current-converter-process
|
|
368 'doc-view-dvi->pdf-sentinel)
|
|
369 (process-put doc-view-current-converter-process 'buffer (current-buffer))
|
|
370 (process-put doc-view-current-converter-process 'pdf-file pdf))
|
|
371
|
|
372 (defun doc-view-pdf/ps->png-sentinel (proc event)
|
|
373 "If PDF/PS->PNG conversion was successful, update the display."
|
|
374 (if (not (string-match "finished" event))
|
|
375 (message "DocView: converter process changed status to %s." event)
|
|
376 (set-buffer (process-get proc 'buffer))
|
|
377 (setq doc-view-current-converter-process nil)
|
|
378 (when doc-view-current-timer
|
|
379 (cancel-timer doc-view-current-timer)
|
|
380 (setq doc-view-current-timer nil))
|
|
381 (message "DocView: finished conversion from PDF/PS to PNG!")
|
|
382 ;; Yippie, finished. Update the display!
|
|
383 (doc-view-display doc-view-current-doc)))
|
|
384
|
|
385 (defun doc-view-pdf/ps->png (pdf-ps png)
|
|
386 "Convert PDF-PS to PNG asynchrounously."
|
|
387 (message "DocView: converting PDF or PS to PNG now!")
|
|
388 (setq doc-view-current-converter-process
|
|
389 (apply 'start-process
|
|
390 (append (list "doc-view-pdf/ps->png" doc-view-conversion-buffer
|
|
391 doc-view-ghostscript-program)
|
|
392 doc-view-ghostscript-options
|
|
393 (list (concat "-sOutputFile=" png))
|
|
394 (list pdf-ps))))
|
|
395 (process-put doc-view-current-converter-process
|
|
396 'buffer (current-buffer))
|
|
397 (set-process-sentinel doc-view-current-converter-process
|
|
398 'doc-view-pdf/ps->png-sentinel)
|
|
399 (when doc-view-conversion-refresh-interval
|
|
400 (setq doc-view-current-timer
|
|
401 (run-at-time "1 secs" doc-view-conversion-refresh-interval
|
|
402 'doc-view-display
|
|
403 doc-view-current-doc))))
|
|
404
|
|
405 (defun doc-view-pdf->txt-sentinel (proc event)
|
|
406 (if (not (string-match "finished" event))
|
|
407 (message "DocView: converter process changed status to %s." event)
|
|
408 (let ((current-buffer (current-buffer))
|
|
409 (proc-buffer (process-get proc 'buffer)))
|
|
410 (set-buffer proc-buffer)
|
|
411 (setq doc-view-current-converter-process nil)
|
|
412 (message "DocView: finished conversion from PDF to TXT!")
|
|
413 ;; If the user looks at the DocView buffer where the conversion was
|
|
414 ;; performed, search anew. This time it will be queried for a regexp.
|
|
415 (when (eq current-buffer proc-buffer)
|
|
416 (doc-view-search)))))
|
|
417
|
|
418 (defun doc-view-pdf->txt (pdf txt)
|
|
419 "Convert PDF to TXT asynchrounously."
|
|
420 (message "DocView: converting PDF to TXT now!")
|
|
421 (setq doc-view-current-converter-process
|
|
422 (start-process "doc-view-pdf->txt" doc-view-conversion-buffer
|
|
423 doc-view-pdftotext-program "-raw"
|
|
424 pdf txt))
|
|
425 (set-process-sentinel doc-view-current-converter-process
|
|
426 'doc-view-pdf->txt-sentinel)
|
|
427 (process-put doc-view-current-converter-process 'buffer (current-buffer)))
|
|
428
|
|
429 (defun doc-view-ps->pdf-sentinel (proc event)
|
|
430 (if (not (string-match "finished" event))
|
|
431 (message "DocView: converter process changed status to %s." event)
|
|
432 (set-buffer (process-get proc 'buffer))
|
|
433 (setq doc-view-current-converter-process nil)
|
|
434 (message "DocView: finished conversion from PS to PDF!")
|
|
435 ;; Now we can transform to plain text.
|
|
436 (doc-view-pdf->txt (process-get proc 'pdf-file)
|
|
437 (concat (doc-view-file-name-to-directory-name
|
|
438 doc-view-current-doc)
|
|
439 "doc.txt"))))
|
|
440
|
|
441 (defun doc-view-ps->pdf (ps pdf)
|
|
442 "Convert PS to PDF asynchronously."
|
|
443 (message "DocView: converting PS to PDF now!")
|
|
444 (setq doc-view-current-converter-process
|
|
445 (start-process "doc-view-ps->pdf" doc-view-conversion-buffer
|
|
446 doc-view-ps2pdf-program
|
|
447 ps pdf))
|
|
448 (set-process-sentinel doc-view-current-converter-process
|
|
449 'doc-view-ps->pdf-sentinel)
|
|
450 (process-put doc-view-current-converter-process 'buffer (current-buffer))
|
|
451 (process-put doc-view-current-converter-process 'pdf-file pdf))
|
|
452
|
|
453 (defun doc-view-convert-doc (doc)
|
|
454 "Convert DOC to a set of png files, one file per page.
|
|
455
|
|
456 Those files are saved in the directory given by
|
|
457 `doc-view-file-name-to-directory-name'."
|
|
458 (clear-image-cache)
|
|
459 (let* ((dir (doc-view-file-name-to-directory-name doc))
|
|
460 (png-file (concat (file-name-as-directory dir) "page-%d.png")))
|
|
461 (when (file-exists-p dir)
|
|
462 (dired-delete-file dir 'always))
|
|
463 (make-directory dir t)
|
|
464 (if (not (string= (file-name-extension doc) "dvi"))
|
|
465 ;; Convert to PNG images.
|
|
466 (doc-view-pdf/ps->png doc png-file)
|
|
467 ;; DVI files have to be converted to PDF before GhostScript can process
|
|
468 ;; it.
|
|
469 (doc-view-dvi->pdf doc
|
|
470 (concat (file-name-as-directory dir)
|
|
471 "doc.pdf")))))
|
|
472
|
|
473 ;;;; DocView Mode
|
|
474
|
|
475 (define-derived-mode doc-view-mode nil "DocView"
|
|
476 "Major mode in DocView buffers.
|
|
477
|
|
478 \\{doc-view-mode-map}"
|
|
479 :group 'doc-view
|
|
480 (setq buffer-read-only t)
|
|
481 (make-local-variable 'doc-view-current-files)
|
|
482 (make-local-variable 'doc-view-current-doc)
|
|
483 (make-local-variable 'doc-view-current-image)
|
|
484 (make-local-variable 'doc-view-current-page)
|
|
485 (make-local-variable 'doc-view-current-converter-process)
|
|
486 (make-local-variable 'doc-view-current-timer)
|
|
487 (make-local-variable 'doc-view-current-slice)
|
|
488 (make-local-variable 'doc-view-current-cache-dir)
|
|
489 (make-local-variable 'doc-view-current-info)
|
|
490 (make-local-variable 'doc-view-current-search-matches))
|
|
491
|
|
492 ;;;; Slicing
|
|
493
|
|
494 (defun doc-view-set-slice (x y width height)
|
|
495 "Set the slice of the images that should be displayed.
|
|
496 You can use this function to tell doc-view not to display the
|
|
497 margins of the document. It prompts for the top-left corner (X
|
|
498 and Y) of the slice to display and its WIDTH and HEIGHT.
|
|
499
|
|
500 See `doc-view-set-slice-using-mouse' for a more convenient way to
|
|
501 do that. To reset the slice use `doc-view-reset-slice'."
|
|
502 (interactive
|
|
503 (let* ((size (image-size doc-view-current-image t))
|
|
504 (a (read-number (format "Top-left X (0..%d): " (car size))))
|
|
505 (b (read-number (format "Top-left Y (0..%d): " (cdr size))))
|
|
506 (c (read-number (format "Width (0..%d): " (- (car size) a))))
|
|
507 (d (read-number (format "Height (0..%d): " (- (cdr size) b)))))
|
|
508 (list a b c d)))
|
|
509 (setq doc-view-current-slice (list x y width height))
|
|
510 ;; Redisplay
|
|
511 (doc-view-goto-page doc-view-current-page))
|
|
512
|
|
513 (defun doc-view-set-slice-using-mouse ()
|
|
514 "Set the slice of the images that should be displayed.
|
|
515 You set the slice by pressing mouse-1 at its top-left corner and
|
|
516 dragging it to its bottom-right corner. See also
|
|
517 `doc-view-set-slice' and `doc-view-reset-slice'."
|
|
518 (interactive)
|
|
519 (let (x y w h done)
|
|
520 (while (not done)
|
|
521 (let ((e (read-event
|
|
522 (concat "Press mouse-1 at the top-left corner and "
|
|
523 "drag it to the bottom-right corner!"))))
|
|
524 (when (eq (car e) 'drag-mouse-1)
|
|
525 (setq x (car (posn-object-x-y (event-start e))))
|
|
526 (setq y (cdr (posn-object-x-y (event-start e))))
|
|
527 (setq w (- (car (posn-object-x-y (event-end e))) x))
|
|
528 (setq h (- (cdr (posn-object-x-y (event-end e))) y))
|
|
529 (setq done t))))
|
|
530 (doc-view-set-slice x y w h)))
|
|
531
|
|
532 (defun doc-view-reset-slice ()
|
|
533 "Resets the current slice.
|
|
534 After calling this function the whole pages will be visible
|
|
535 again."
|
|
536 (interactive)
|
|
537 (setq doc-view-current-slice nil)
|
|
538 ;; Redisplay
|
|
539 (doc-view-goto-page doc-view-current-page))
|
|
540
|
|
541 ;;;; Display
|
|
542
|
|
543 (defun doc-view-insert-image (file &rest args)
|
|
544 "Insert the given png FILE.
|
|
545 ARGs is a list of image descriptors."
|
|
546 (let ((image (apply 'create-image file 'png nil args)))
|
|
547 (setq doc-view-current-image image)
|
|
548 (insert-image image (concat "[" file "]") nil doc-view-current-slice)))
|
|
549
|
|
550 (defun doc-view-sort (a b)
|
|
551 "Return non-nil if A should be sorted before B.
|
|
552 Predicate for sorting `doc-view-current-files'."
|
|
553 (if (< (length a) (length b))
|
|
554 t
|
|
555 (if (> (length a) (length b))
|
|
556 nil
|
|
557 (string< a b))))
|
|
558
|
|
559 (defun doc-view-display (doc)
|
|
560 "Start viewing the document DOC."
|
|
561 (let ((dir (doc-view-file-name-to-directory-name doc)))
|
|
562 (set-buffer (format "*DocView: %s*" doc))
|
|
563 (setq doc-view-current-files
|
|
564 (sort (directory-files dir t "page-[0-9]+\\.png" t)
|
|
565 'doc-view-sort))
|
|
566 (when (> (length doc-view-current-files) 0)
|
|
567 (doc-view-goto-page doc-view-current-page))))
|
|
568
|
|
569 (defun doc-view-buffer-message ()
|
|
570 (setq inhibit-read-only t)
|
|
571 (erase-buffer)
|
|
572 (insert (propertize "Welcome to DocView!" 'face 'bold)
|
|
573 "\n"
|
|
574 "
|
|
575 If you see this buffer it means that the document you want to
|
|
576 view gets converted to PNG now and the conversion of the first
|
|
577 page hasn't finished yet or
|
|
578 `doc-view-conversion-refresh-interval' is set to nil.
|
|
579
|
|
580 For now these keys are useful:
|
|
581
|
|
582 `q' : Bury this buffer. Conversion will go on in background.
|
|
583 `k' : Kill the conversion process and this buffer.\n")
|
|
584 (setq inhibit-read-only nil))
|
|
585
|
|
586 (defun doc-view-show-tooltip ()
|
|
587 (interactive)
|
|
588 (tooltip-show doc-view-current-info))
|
|
589
|
|
590 ;;;; Searching
|
|
591
|
|
592 (defun doc-view-search-internal (regexp file)
|
|
593 "Return a list of FILE's pages that contain text matching REGEXP.
|
|
594 The value is an alist of the form
|
|
595
|
|
596 (PAGE CONTEXTS)
|
|
597
|
|
598 where PAGE is the pagenumber and CONTEXTS are the lines
|
|
599 containing the match."
|
|
600 (with-temp-buffer
|
|
601 (insert-file-contents file)
|
|
602 (let ((page 1)
|
|
603 (lastpage 1)
|
|
604 matches)
|
|
605 (while (re-search-forward (concat "\\(?:\\([]\\)\\|\\("
|
|
606 regexp "\\)\\)") nil t)
|
|
607 (when (match-string 1) (incf page))
|
|
608 (when (match-string 2)
|
|
609 (if (/= page lastpage)
|
|
610 (setq matches (push (cons page
|
|
611 (list (buffer-substring
|
|
612 (line-beginning-position)
|
|
613 (line-end-position))))
|
|
614 matches))
|
|
615 (setq matches (cons
|
|
616 (append
|
|
617 (or
|
|
618 ;; This page already is a match.
|
|
619 (car matches)
|
|
620 ;; This is the first match on page.
|
|
621 (list page))
|
|
622 (list (buffer-substring
|
|
623 (line-beginning-position)
|
|
624 (line-end-position))))
|
|
625 (cdr matches))))
|
|
626 (setq lastpage page)))
|
|
627 (nreverse matches))))
|
|
628
|
|
629 (defun doc-view-search-no-of-matches (list)
|
|
630 "Extract the number of matches from the search result LIST."
|
|
631 (let ((no 0))
|
|
632 (dolist (p list)
|
|
633 (setq no (+ no (1- (length p)))))
|
|
634 no))
|
|
635
|
|
636 (defun doc-view-search ()
|
|
637 "Query for a regexp and search the current document.
|
|
638 If the current document hasn't been transformed to plain text
|
|
639 till now do that first. You should try searching anew when the
|
|
640 conversion finished."
|
|
641 (interactive)
|
|
642 ;; New search, so forget the old results.
|
|
643 (setq doc-view-current-search-matches nil)
|
|
644 (let ((txt (concat (doc-view-file-name-to-directory-name
|
|
645 doc-view-current-doc)
|
|
646 "doc.txt")))
|
|
647 (if (file-readable-p txt)
|
|
648 (progn
|
|
649 (setq doc-view-current-search-matches
|
|
650 (doc-view-search-internal
|
|
651 (read-from-minibuffer "Regexp: ")
|
|
652 txt))
|
|
653 (message "DocView: search yielded %d matches."
|
|
654 (doc-view-search-no-of-matches
|
|
655 doc-view-current-search-matches)))
|
|
656 ;; We must convert to TXT first!
|
|
657 (if doc-view-current-converter-process
|
|
658 (message "DocView: please wait till conversion finished.")
|
|
659 (let ((ext (file-name-extension doc-view-current-doc)))
|
|
660 (cond
|
|
661 ((string= ext "pdf")
|
|
662 ;; Doc is a PDF, so convert it to TXT
|
|
663 (doc-view-pdf->txt doc-view-current-doc txt))
|
|
664 ((string= ext "ps")
|
|
665 ;; Doc is a PS, so convert it to PDF (which will be converted to
|
|
666 ;; TXT thereafter).
|
|
667 (doc-view-ps->pdf doc-view-current-doc
|
|
668 (concat (doc-view-file-name-to-directory-name
|
|
669 doc-view-current-doc)
|
|
670 "doc.pdf")))
|
|
671 ((string= ext "dvi")
|
|
672 ;; Doc is a DVI. This means that a doc.pdf already exists in its
|
|
673 ;; cache subdirectory.
|
|
674 (doc-view-pdf->txt (concat (doc-view-file-name-to-directory-name
|
|
675 doc-view-current-doc)
|
|
676 "doc.pdf")
|
|
677 txt))
|
|
678 (t (error "DocView doesn't know what to do"))))))))
|
|
679
|
|
680 (defun doc-view-search-next-match (arg)
|
|
681 "Go to the ARGth next matching page."
|
|
682 (interactive "p")
|
|
683 (let* ((next-pages (remove-if (lambda (i) (<= (car i) doc-view-current-page))
|
|
684 doc-view-current-search-matches))
|
|
685 (page (car (nth (1- arg) next-pages))))
|
|
686 (if page
|
|
687 (doc-view-goto-page page)
|
|
688 (when (and
|
|
689 doc-view-current-search-matches
|
|
690 (y-or-n-p "No more matches after current page. Wrap to first match? "))
|
|
691 (doc-view-goto-page (caar doc-view-current-search-matches))))))
|
|
692
|
|
693 (defun doc-view-search-previous-match (arg)
|
|
694 "Go to the ARGth previous matching page."
|
|
695 (interactive "p")
|
|
696 (let* ((prev-pages (remove-if (lambda (i) (>= (car i) doc-view-current-page))
|
|
697 doc-view-current-search-matches))
|
|
698 (page (car (nth (1- arg) (nreverse prev-pages)))))
|
|
699 (if page
|
|
700 (doc-view-goto-page page)
|
|
701 (when (and
|
|
702 doc-view-current-search-matches
|
|
703 (y-or-n-p "No more matches before current page. Wrap to last match? "))
|
|
704 (doc-view-goto-page (caar (last doc-view-current-search-matches)))))))
|
|
705
|
|
706 ;;;; User Interface Commands
|
|
707
|
|
708 (defun doc-view (no-cache &optional file)
|
|
709 "Convert FILE to png and start viewing it.
|
|
710 If no FILE is given, query for on.
|
|
711 If this FILE is still in the cache, don't convert and use the
|
|
712 existing page files. With prefix arg NO-CACHE, don't use the
|
|
713 cached files and convert anew."
|
|
714 (interactive "P")
|
|
715 (if (not (and (image-type-available-p 'png)
|
|
716 (display-images-p)))
|
|
717 (message "DocView: your emacs or display doesn't support png images.")
|
|
718 (let* ((doc (or file
|
|
719 (expand-file-name (read-file-name "File: " nil nil t))))
|
|
720 (buffer (get-buffer-create (format "*DocView: %s*" doc)))
|
|
721 (dir (doc-view-file-name-to-directory-name doc)))
|
|
722 (switch-to-buffer buffer)
|
|
723 (doc-view-buffer-message)
|
|
724 (doc-view-mode)
|
|
725 (setq doc-view-current-doc doc)
|
|
726 (setq doc-view-current-page 1)
|
|
727 (if (not (and (file-exists-p dir)
|
|
728 (not no-cache)))
|
|
729 (progn
|
|
730 (setq doc-view-current-cache-dir nil)
|
|
731 (doc-view-convert-doc doc-view-current-doc))
|
|
732 (message "DocView: using cached files!")
|
|
733 (doc-view-display doc-view-current-doc)))))
|
|
734
|
|
735 (defun doc-view-dired (no-cache)
|
|
736 "View the current dired file with doc-view.
|
|
737 NO-CACHE is the same as in `doc-view'.
|
|
738
|
|
739 You might want to bind this command to a dired key, e.g.
|
|
740
|
|
741 (define-key dired-mode-map (kbd \"C-c d\") 'doc-view-dired)"
|
|
742 (interactive "P")
|
|
743 (doc-view no-cache (dired-get-file-for-visit)))
|
|
744
|
|
745 (defun doc-view-clear-cache ()
|
|
746 "Delete the whole cache (`doc-view-cache-directory')."
|
|
747 (interactive)
|
|
748 (dired-delete-file doc-view-cache-directory 'always)
|
|
749 (make-directory doc-view-cache-directory))
|
|
750
|
|
751 (defun doc-view-dired-cache ()
|
|
752 "Open `dired' in `doc-view-cache-directory'."
|
|
753 (interactive)
|
|
754 (dired doc-view-cache-directory))
|
|
755
|
|
756 (provide 'doc-view)
|
|
757
|
|
758 ;; Local Variables:
|
|
759 ;; mode: outline-minor
|
|
760 ;; End:
|
|
761
|
84450
|
762 ;; arch-tag: 5d6e5c5e-095f-489e-b4e4-1ca90a7d79be
|
84422
|
763 ;;; doc-view.el ends here
|