X-Git-Url: https://code.delx.au/gnu-emacs/blobdiff_plain/6664fc59a8f296117ea98b943f062c0cc0e907c1..3c7ee4f39aabaeab09ffe5e1ab5d9ebba22652a6:/lisp/doc-view.el diff --git a/lisp/doc-view.el b/lisp/doc-view.el index 4f183f4b9d..872b2172c7 100644 --- a/lisp/doc-view.el +++ b/lisp/doc-view.el @@ -1,6 +1,7 @@ -;;; doc-view.el --- View PDF/PostScript/DVI files in Emacs +;;; doc-view.el --- View PDF/PostScript/DVI files in Emacs -*- lexical-binding: t -*- -;; Copyright (C) 2007, 2008, 2009, 2010 Free Software Foundation, Inc. + +;; Copyright (C) 2007-2011 Free Software Foundation, Inc. ;; ;; Author: Tassilo Horn ;; Maintainer: Tassilo Horn @@ -155,7 +156,7 @@ (defcustom doc-view-ghostscript-options '("-dSAFER" ;; Avoid security problems when rendering files from untrusted - ;; sources. + ;; sources. "-dNOPAUSE" "-sDEVICE=png16m" "-dTextAlphaBits=4" "-dBATCH" "-dGraphicsAlphaBits=4" "-dQUIET") "A list of options to give to ghostscript." @@ -168,6 +169,12 @@ Higher values result in larger images." :type 'number :group 'doc-view) +(defcustom doc-view-image-width 850 + "Default image width. +Has only an effect if imagemagick support is compiled into emacs." + :type 'number + :group 'doc-view) + (defcustom doc-view-dvipdfm-program (executable-find "dvipdfm") "Program to convert DVI files to PDF. @@ -190,6 +197,13 @@ If this and `doc-view-dvipdfm-program' are set, :type 'file :group 'doc-view) +(defcustom doc-view-unoconv-program (executable-find "unoconv") + "Program to convert any file type readable by OpenOffice.org to PDF. + +Needed for viewing OpenOffice.org (and MS Office) files." + :type 'file + :group 'doc-view) + (defcustom doc-view-ps2pdf-program (executable-find "ps2pdf") "Program to convert PS files to PDF. @@ -314,6 +328,10 @@ Can be `dvi', `pdf', or `ps'.") ;; Zoom in/out. (define-key map "+" 'doc-view-enlarge) (define-key map "-" 'doc-view-shrink) + ;; 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) ;; Killing the buffer (and the process) (define-key map (kbd "k") 'doc-view-kill-proc-and-buffer) (define-key map (kbd "K") 'doc-view-kill-proc) @@ -429,9 +447,7 @@ Can be `dvi', `pdf', or `ps'.") 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 - (lexical-let ((page page) - (win (selected-window)) - (file file)) + (let ((win (selected-window))) (lambda () (and (eq (current-buffer) (window-buffer win)) ;; If we changed page in the mean @@ -440,7 +456,7 @@ Can be `dvi', `pdf', or `ps'.") ;; Make sure we don't infloop. (file-readable-p file) (with-selected-window win - (doc-view-goto-page page)))))))) + (doc-view-goto-page page)))))))) (overlay-put (doc-view-current-overlay) 'help-echo (doc-view-current-info)))) @@ -553,18 +569,18 @@ at the top edge of the page moves to the previous page." (defun doc-view-make-safe-dir (dir) (condition-case nil (let ((umask (default-file-modes))) - (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))) + (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))) (file-already-exists - (if (file-symlink-p dir) - (error "Danger: %s points to a symbolic link" dir)) + (when (file-symlink-p dir) + (error "Danger: %s points to a symbolic link" dir)) ;; 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 @@ -573,7 +589,12 @@ at the top edge of the page moves to the previous page." ;; 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. - (set-file-modes dir #o0700)))) + (condition-case error + (set-file-modes dir #o0700) + (file-error + (error + (format "Unable to use temporary directory %s: %s" + dir (mapconcat 'identity (cdr error) " ")))))))) (defun doc-view-current-cache-dir () "Return the directory where the png files of the current doc should be saved. @@ -598,16 +619,19 @@ It's a subdirectory of `doc-view-cache-directory'." (defun doc-view-remove-if (predicate list) "Return LIST with all items removed that satisfy PREDICATE." (let (new-list) - (dolist (item list (nreverse new-list)) + (dolist (item list) (when (not (funcall predicate item)) - (setq new-list (cons item new-list)))))) + (setq new-list (cons item new-list)))) + (nreverse new-list))) ;;;###autoload (defun doc-view-mode-p (type) - "Return non-nil if image type TYPE is available for `doc-view'. -Image types are symbols like `dvi', `postscript' or `pdf'." + "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)." (and (display-graphic-p) - (image-type-available-p 'png) + (or (image-type-available-p 'imagemagick) + (image-type-available-p 'png)) (cond ((eq type 'dvi) (and (doc-view-mode-p 'pdf) @@ -619,6 +643,10 @@ Image types are symbols like `dvi', `postscript' or `pdf'." (eq type 'pdf)) (and doc-view-ghostscript-program (executable-find doc-view-ghostscript-program))) + ((eq type 'odf) + (and doc-view-unoconv-program + (executable-find doc-view-unoconv-program) + (doc-view-mode-p 'pdf))) (t ;; unknown image type nil)))) @@ -629,15 +657,95 @@ Image types are symbols like `dvi', `postscript' or `pdf'." (defun doc-view-enlarge (factor) "Enlarge the document." (interactive (list doc-view-shrink-factor)) - (set (make-local-variable 'doc-view-resolution) - (* factor doc-view-resolution)) - (doc-view-reconvert-doc)) + (if (eq (plist-get (cdr (doc-view-current-image)) :type) + 'imagemagick) + ;; ImageMagick supports on-the-fly-rescaling + (progn + (set (make-local-variable 'doc-view-image-width) + (ceiling (* factor doc-view-image-width))) + (doc-view-insert-image (plist-get (cdr (doc-view-current-image)) :file) + :width doc-view-image-width)) + (set (make-local-variable 'doc-view-resolution) + (ceiling (* factor doc-view-resolution))) + (doc-view-reconvert-doc))) (defun doc-view-shrink (factor) "Shrink the document." (interactive (list doc-view-shrink-factor)) (doc-view-enlarge (/ 1.0 factor))) +(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)) + (/ (float (- win-height 1)) (float img-height))))) + ;; If slice is set + (let* ((slice-width (nth 2 slice)) + (slice-height (nth 3 slice)) + (scale-factor (min (/ (float win-width) (float slice-width)) + (/ (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-reconvert-doc () "Reconvert the current document. Should be invoked when the cached images aren't up-to-date." @@ -686,12 +794,19 @@ Should be invoked when the cached images aren't up-to-date." (if (and doc-view-dvipdf-program (executable-find doc-view-dvipdf-program)) (doc-view-start-process "dvi->pdf" doc-view-dvipdf-program - (list dvi pdf) - callback) + (list dvi pdf) + callback) (doc-view-start-process "dvi->pdf" doc-view-dvipdfm-program (list "-o" pdf dvi) callback))) +(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)) (defun doc-view-pdf/ps->png (pdf-ps png) "Convert PDF-PS to PNG asynchronously." @@ -701,7 +816,7 @@ Should be invoked when the cached images aren't up-to-date." (list (format "-r%d" (round doc-view-resolution)) (concat "-sOutputFile=" png) pdf-ps)) - (lexical-let ((resolution doc-view-resolution)) + (let ((resolution doc-view-resolution)) (lambda () ;; Only create the resolution file when it's all done, so it also ;; serves as a witness that the conversion is complete. @@ -746,7 +861,7 @@ Start by converting PAGES, and then the rest." ;; (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. - (lexical-let ((pdf pdf) (png png) (rest (cdr pages))) + (let ((rest (cdr pages))) (doc-view-pdf->png-1 pdf (format png (car pages)) (car pages) (lambda () @@ -759,8 +874,8 @@ Start by converting PAGES, and then the rest." ;; not sufficient. (dolist (win (get-buffer-window-list (current-buffer) nil 'visible)) (with-selected-window win - (when (stringp (get-char-property (point-min) 'display)) - (doc-view-goto-page (doc-view-current-page))))) + (when (stringp (get-char-property (point-min) 'display)) + (doc-view-goto-page (doc-view-current-page))))) ;; Convert the rest of the pages. (doc-view-pdf/ps->png pdf png))))))) @@ -782,10 +897,8 @@ Start by converting PAGES, and then the rest." (ps ;; Doc is a PS, so convert it to PDF (which will be converted to ;; TXT thereafter). - (lexical-let ((pdf (expand-file-name "doc.pdf" - (doc-view-current-cache-dir))) - (txt txt) - (callback callback)) + (let ((pdf (expand-file-name "doc.pdf" + (doc-view-current-cache-dir)))) (doc-view-ps->pdf doc-view-buffer-file-name pdf (lambda () (doc-view-pdf->txt pdf txt callback))))) (dvi @@ -794,6 +907,12 @@ Start by converting PAGES, and then the rest." (doc-view-pdf->txt (expand-file-name "doc.pdf" (doc-view-current-cache-dir)) txt callback)) + (odf + ;; 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)) (t (error "DocView doesn't know what to do")))) (defun doc-view-ps->pdf (ps pdf callback) @@ -833,11 +952,27 @@ Those files are saved in the directory given by the function (dvi ;; DVI files have to be converted to PDF before Ghostscript can process ;; it. + (let ((pdf (expand-file-name "doc.pdf" doc-view-current-cache-dir))) + (doc-view-dvi->pdf doc-view-buffer-file-name pdf + (lambda () (doc-view-pdf/ps->png pdf png-file))))) + (odf + ;; ODF files have to be converted to PDF before Ghostscript can + ;; process it. (lexical-let ((pdf (expand-file-name "doc.pdf" doc-view-current-cache-dir)) + (opdf (expand-file-name (concat (file-name-sans-extension + (file-name-nondirectory doc-view-buffer-file-name)) + ".pdf") + doc-view-current-cache-dir)) (png-file png-file)) - (doc-view-dvi->pdf doc-view-buffer-file-name pdf - (lambda () (doc-view-pdf/ps->png pdf png-file))))) + ;; The unoconv tool only supports a output directory, but no + ;; file name. It's named like the input file with the + ;; extension replaced by pdf. + (doc-view-odf->pdf doc-view-buffer-file-name + (lambda () + ;; Rename to doc.pdf + (rename-file opdf pdf) + (doc-view-pdf/ps->png pdf png-file))))) (pdf (let ((pages (doc-view-active-pages))) ;; Convert PDF to PNG images starting with the active pages. @@ -906,7 +1041,11 @@ ARGS is a list of image descriptors." (setq doc-view-pending-cache-flush nil)) (let ((ol (doc-view-current-overlay)) (image (if (and file (file-readable-p file)) - (apply 'create-image file 'png nil args))) + (if (not (fboundp 'imagemagick-types)) + (apply 'create-image file 'png nil args) + (unless (member :width args) + (setq args (append args (list :width doc-view-image-width)))) + (apply 'create-image file 'imagemagick nil args)))) (slice (doc-view-current-slice))) (setf (doc-view-current-image) image) (move-overlay ol (point-min) (point-max)) @@ -964,8 +1103,8 @@ have the page we want to view." (and (not (member pagefile prev-pages)) (member pagefile doc-view-current-files))) (with-selected-window win - (assert (eq (current-buffer) buffer)) - (doc-view-goto-page page)))))))) + (assert (eq (current-buffer) buffer)) + (doc-view-goto-page page)))))))) (defun doc-view-buffer-message () ;; Only show this message initially, not when refreshing the buffer (in which @@ -999,12 +1138,16 @@ For now these keys are useful: (message "DocView: please wait till conversion finished.") (let ((txt (expand-file-name "doc.txt" (doc-view-current-cache-dir)))) (if (file-readable-p txt) - (find-file txt) + (let ((name (concat "Text contents of " + (file-name-nondirectory buffer-file-name))) + (dir (file-name-directory buffer-file-name))) + (with-current-buffer (find-file txt) + (rename-buffer name) + (setq default-directory dir))) (doc-view-doc->txt txt 'doc-view-open-text))))) ;;;;; Toggle between editing and viewing - (defun doc-view-toggle-display () "Toggle between editing a document as text or viewing it." (interactive) @@ -1015,11 +1158,9 @@ For now these keys are useful: (setq buffer-read-only nil) (remove-overlays (point-min) (point-max) 'doc-view t) (set (make-local-variable 'image-mode-winprops-alist) t) - ;; Switch to the previously used major mode or fall back to fundamental - ;; mode. - (if doc-view-previous-major-mode - (funcall doc-view-previous-major-mode) - (fundamental-mode)) + ;; Switch to the previously used major mode or fall back to + ;; normal mode. + (doc-view-fallback-mode) (doc-view-minor-mode 1)) ;; Switch to doc-view-mode (when (and (buffer-modified-p) @@ -1179,11 +1320,11 @@ If BACKWARD is non-nil, jump to the previous match." (concat "No PNG support is available, or some conversion utility for " (file-name-extension doc-view-buffer-file-name) " files is missing.")) - (if (and (executable-find doc-view-pdftotext-program) - (y-or-n-p - "Unable to render file. View extracted text instead? ")) - (doc-view-open-text) - (doc-view-toggle-display)))) + (when (and (executable-find doc-view-pdftotext-program) + (y-or-n-p + "Unable to render file. View extracted text instead? ")) + (doc-view-open-text)) + (doc-view-toggle-display))) (defvar bookmark-make-record-function) @@ -1206,6 +1347,41 @@ If BACKWARD is non-nil, jump to the previous match." (dolist (x l1) (if (memq x l2) (push x l))) l)) +(defun doc-view-set-doc-type () + "Figure out the current document type (`doc-view-doc-type')." + (let ((name-types + (when buffer-file-name + (cdr (assoc (file-name-extension buffer-file-name) + '( + ;; DVI + ("dvi" dvi) + ;; PDF + ("pdf" pdf) ("epdf" pdf) + ;; PostScript + ("ps" ps) ("eps" ps) + ;; OpenDocument formats + ("odt" odf) ("ods" odf) ("odp" odf) ("odg" odf) + ("odc" odf) ("odi" odf) ("odm" odf) ("ott" odf) + ("ots" odf) ("otp" odf) ("otg" odf) + ;; Microsoft Office formats (also handled + ;; by the odf conversion chain) + ("doc" odf) ("docx" odf) ("xls" odf) ("xlsx" odf) + ("ppt" odf) ("pptx" odf)))))) + (content-types + (save-excursion + (goto-char (point-min)) + (cond + ((looking-at "%!") '(ps)) + ((looking-at "%PDF") '(pdf)) + ((looking-at "\367\002") '(dvi)))))) + (set (make-local-variable 'doc-view-doc-type) + (car (or (doc-view-intersection name-types content-types) + (when (and name-types content-types) + (error "Conflicting types: name says %s but content says %s" + name-types content-types)) + name-types content-types + (error "Cannot determine the document type")))))) + ;;;###autoload (defun doc-view-mode () "Major mode in DocView buffers. @@ -1222,39 +1398,19 @@ toggle between displaying the document or editing it as text. ;; The doc is empty or doesn't exist at all, so fallback to ;; another mode. We used to also check file-exists-p, but this ;; returns nil for tar members. - (let ((auto-mode-alist (remq (rassq 'doc-view-mode auto-mode-alist) - auto-mode-alist))) - (normal-mode)) + (doc-view-fallback-mode) (let* ((prev-major-mode (if (eq major-mode 'doc-view-mode) doc-view-previous-major-mode - major-mode))) + (when (not (memq major-mode + '(doc-view-mode fundamental-mode))) + major-mode)))) (kill-all-local-variables) (set (make-local-variable 'doc-view-previous-major-mode) prev-major-mode)) ;; Figure out the document type. - (let ((name-types - (when buffer-file-name - (cdr (assoc (file-name-extension buffer-file-name) - '(("dvi" dvi) - ("pdf" pdf) - ("epdf" pdf) - ("ps" ps) - ("eps" ps)))))) - (content-types - (save-excursion - (goto-char (point-min)) - (cond - ((looking-at "%!") '(ps)) - ((looking-at "%PDF") '(pdf)) - ((looking-at "\367\002") '(dvi)))))) - (set (make-local-variable 'doc-view-doc-type) - (car (or (doc-view-intersection name-types content-types) - (when (and name-types content-types) - (error "Conflicting types: name says %s but content says %s" - name-types content-types)) - name-types content-types - (error "Cannot determine the document type"))))) + (unless doc-view-doc-type + (doc-view-set-doc-type)) (doc-view-make-safe-dir doc-view-cache-directory) ;; Handle compressed files, remote files, files inside archives @@ -1322,6 +1478,28 @@ toggle between displaying the document or editing it as text. (set (make-local-variable 'view-read-only) nil) (run-mode-hooks 'doc-view-mode-hook))) +(defun doc-view-fallback-mode () + "Fallback to the previous or next best major mode." + (if doc-view-previous-major-mode + (funcall doc-view-previous-major-mode) + (let ((auto-mode-alist (rassq-delete-all + 'doc-view-mode-maybe + (rassq-delete-all 'doc-view-mode + (copy-alist auto-mode-alist))))) + (normal-mode)))) + +;;;###autoload +(defun doc-view-mode-maybe () + "Switch to `doc-view-mode' if possible. +If the required external tools are not available, then fallback +to the next best mode." + (condition-case nil + (doc-view-set-doc-type) + (error (doc-view-fallback-mode))) + (if (doc-view-mode-p doc-view-doc-type) + (doc-view-mode) + (doc-view-fallback-mode))) + ;;;###autoload (define-minor-mode doc-view-minor-mode "Toggle Doc view minor mode. @@ -1369,16 +1547,15 @@ See the command `doc-view-mode' for more information on this mode." (when (not (eq major-mode 'doc-view-mode)) (doc-view-toggle-display)) (with-selected-window - (or (get-buffer-window (current-buffer) 0) - (selected-window)) - (doc-view-goto-page page))))) + (or (get-buffer-window (current-buffer) 0) + (selected-window)) + (doc-view-goto-page page))))) (provide 'doc-view) ;; Local Variables: -;; mode: outline-minor +;; eval: (outline-minor-mode 1) ;; End: -;; arch-tag: 5d6e5c5e-095f-489e-b4e4-1ca90a7d79be ;;; doc-view.el ends here