;;; doc-view.el --- View PDF/PostScript/DVI files in Emacs -*- lexical-binding: t -*-
-;; Copyright (C) 2007-2012 Free Software Foundation, Inc.
+;; Copyright (C) 2007-2013 Free Software Foundation, Inc.
;;
-;; Author: Tassilo Horn <tassilo@member.fsf.org>
-;; Maintainer: Tassilo Horn <tassilo@member.fsf.org>
+;; Author: Tassilo Horn <tsdh@gnu.org>
+;; Maintainer: Tassilo Horn <tsdh@gnu.org>
;; Keywords: files, pdf, ps, dvi
;; This file is part of GNU Emacs.
;; pages won't be displayed before conversion of the document finished
;; completely.
;;
-;; 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. To reset the slice use
-;; `doc-view-reset-slice' (bound to `s r').
+;; 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').
;;
;; 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
;; - share more code with image-mode.
;; - better menu.
;; - Bind slicing to a drag event.
-;; - doc-view-fit-doc-to-window and doc-view-fit-window-to-doc?
;; - zoom the region around the cursor (like xdvi).
;; - get rid of the silly arrow in the fringe.
;; - improve anti-aliasing (pdf-utils gets it better).
;;;; Customization Options
(defgroup doc-view nil
- "In-buffer viewer for PDF, PostScript and DVI files."
+ "In-buffer viewer for PDF, PostScript, DVI, and DJVU files."
:link '(function-link doc-view)
:version "22.2"
:group 'applications
:type 'file
:group 'doc-view)
+(defcustom doc-view-pdfdraw-program
+ (cond
+ ((executable-find "pdfdraw") "pdfdraw")
+ (t "mudraw"))
+ "Name of MuPDF's program to convert PDF files to PNG."
+ :type 'file
+ :version "24.4")
+
+(defcustom doc-view-pdf->png-converter-function
+ (if (executable-find doc-view-pdfdraw-program)
+ #'doc-view-pdf->png-converter-mupdf
+ #'doc-view-pdf->png-converter-ghostscript)
+ "Function to call to convert a PDF file into a PNG file."
+ :type '(radio
+ (function-item doc-view-pdf->png-converter-ghostscript
+ :doc "Use ghostscript")
+ (function-item doc-view-pdf->png-converter-mupdf
+ :doc "Use mupdf")
+ function)
+ :version "24.4")
+
(defcustom doc-view-ghostscript-options
'("-dSAFER" ;; Avoid security problems when rendering files from untrusted
;; sources.
:type 'number
:group 'doc-view)
+(defcustom doc-view-scale-internally t
+ "Whether we should try to rescale images ourselves.
+If nil, the document is re-rendered every time the scaling factor is modified.
+This only has an effect if the image libraries linked with Emacs support
+scaling."
+ :type 'boolean)
+
(defcustom doc-view-image-width 850
"Default image width.
-Has only an effect if imagemagick support is compiled into emacs."
+Has only an effect if `doc-view-scale-internally' is non-nil and support for
+scaling is compiled into emacs."
:version "24.1"
:type 'number
:group 'doc-view)
;;;; Internal Variables
(defun doc-view-new-window-function (winprops)
+ ;; (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))))
(let ((ol (image-mode-window-get 'overlay winprops)))
- (when (and ol (not (overlay-buffer ol)))
- ;; I've seen `ol' be a dead overlay. I do not yet know how this
- ;; happened, so maybe the bug is elsewhere, but in the mean time,
- ;; this seems like a safe approach.
- (setq ol nil))
(if ol
(progn
- (cl-assert (eq (overlay-buffer ol) (current-buffer)))
- (setq ol (copy-overlay ol)))
- (cl-assert (not (get-char-property (point-min) 'display)))
+ (setq ol (copy-overlay ol))
+ ;; `ol' might actually be dead.
+ (move-overlay ol (point-min) (point-max)))
(setq ol (make-overlay (point-min) (point-max) nil t))
(overlay-put ol 'doc-view t))
(overlay-put ol 'window (car winprops))
+ (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))
(image-mode-window-put 'overlay ol winprops)))
(defvar doc-view-current-files nil
"The type of document in the current buffer.
Can be `dvi', `pdf', or `ps'.")
+(defvar doc-view-single-page-converter-function nil
+ "Function to call to convert a single page of the document to a bitmap file.
+May operate on the source document or on some intermediate (typically PDF)
+conversion of it.")
+
+(defvar-local doc-view--image-type nil
+ "The type of image in the current buffer.
+Can be `png' or `tiff'.")
+
+(defvar-local doc-view--image-file-pattern nil
+ "The `format' pattern of image file names.
+Typically \"page-%s.png\".")
+
;;;; DocView Keymaps
(defvar doc-view-mode-map
;; 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)
+ (define-key map (kbd "s b") 'doc-view-set-slice-from-bounding-box)
(define-key map (kbd "s r") 'doc-view-reset-slice)
;; Searching
(define-key map (kbd "C-s") 'doc-view-search)
)
"---"
["Set Slice" doc-view-set-slice-using-mouse]
+ ["Set Slice (BoundingBox)" doc-view-set-slice-from-bounding-box]
["Set Slice (manual)" doc-view-set-slice]
["Reset Slice" doc-view-reset-slice]
"---"
;; 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.
- (let ((file (expand-file-name (format "page-%d.png" page)
- (doc-view-current-cache-dir))))
+ (let ((file (expand-file-name
+ (format doc-view--image-file-pattern page)
+ (doc-view-current-cache-dir))))
(doc-view-insert-image file :pointer 'arrow)
(set-window-hscroll (selected-window) hscroll)
(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
- (let ((win (selected-window)))
- (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)
- ;; Make sure we don't infloop.
- (file-readable-p file)
- (with-selected-window win
- (doc-view-goto-page page))))))))
+ (funcall doc-view-single-page-converter-function
+ doc-view-buffer-file-name file page
+ (let ((win (selected-window)))
+ (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)
+ ;; Make sure we don't infloop.
+ (file-readable-p file)
+ (with-selected-window win
+ (doc-view-goto-page page))))))))
(overlay-put (doc-view-current-overlay)
'help-echo (doc-view-current-info))))
"Kill the current converter process(es)."
(interactive)
(while (consp doc-view-current-converter-processes)
- (ignore-errors ;; Maybe it's dead already?
+ (ignore-errors ;; Some entries might not be processes, and maybe
+ ;; some are dead already?
(kill-process (pop doc-view-current-converter-processes))))
(when doc-view-current-timer
(cancel-timer doc-view-current-timer)
(executable-find doc-view-dvipdf-program))
(and doc-view-dvipdfm-program
(executable-find doc-view-dvipdfm-program)))))
- ((or (eq type 'postscript) (eq type 'ps) (eq type 'eps)
- (eq type 'pdf))
+ ((memq type '(postscript ps eps pdf))
+ ;; FIXME: allow mupdf here
(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)))
+ ((eq type 'djvu)
+ (executable-find "ddjvu"))
(t ;; unknown image type
nil))))
(defvar doc-view-shrink-factor 1.125)
(defun doc-view-enlarge (factor)
- "Enlarge the document."
+ "Enlarge the document by FACTOR."
(interactive (list doc-view-shrink-factor))
- (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)))
+ (if (and doc-view-scale-internally
+ (eq (plist-get (cdr (doc-view-current-image)) :type)
+ 'imagemagick))
+ ;; ImageMagick supports on-the-fly-rescaling.
+ (let ((new (ceiling (* factor doc-view-image-width))))
+ (unless (equal new doc-view-image-width)
+ (setq-local 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)
+ (setq-local doc-view-resolution new)
+ (doc-view-reconvert-doc)))))
(defun doc-view-shrink (factor)
"Shrink the document."
(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)))))
+ (/ (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))))
+ (/ (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)
;; Clear the old cached files
(when (file-exists-p (doc-view-current-cache-dir))
(delete-directory (doc-view-current-cache-dir) 'recursive))
+ (kill-local-variable 'doc-view-last-page-number)
(doc-view-initiate-display))
(defun doc-view-sentinel (proc event)
(list "-o" pdf dvi)
callback)))
+(defun doc-view-pdf->png-converter-ghostscript (pdf png page callback)
+ (doc-view-start-process
+ "pdf/ps->png" doc-view-ghostscript-program
+ `(,@doc-view-ghostscript-options
+ ,(format "-r%d" (round doc-view-resolution))
+ ,@(if page `(,(format "-dFirstPage=%d" page)))
+ ,@(if page `(,(format "-dLastPage=%d" page)))
+ ,(concat "-sOutputFile=" png)
+ ,pdf)
+ callback))
+
+(defalias 'doc-view-ps->png-converter-ghostscript
+ 'doc-view-pdf->png-converter-ghostscript)
+
+(defun doc-view-djvu->tiff-converter-ddjvu (djvu tiff page callback)
+ "Convert PAGE of a DJVU file to bitmap(s) asynchronously.
+Call CALLBACK with no arguments when done.
+If PAGE is nil, convert the whole document."
+ (doc-view-start-process
+ "djvu->tiff" "ddjvu"
+ `("-format=tiff"
+ ;; ddjvu only accepts the range 1-999.
+ ,(format "-scale=%d" (round doc-view-resolution))
+ ;; -eachpage was only added after djvulibre-3.5.25.3!
+ ,@(unless page '("-eachpage"))
+ ,@(if page `(,(format "-page=%d" page)))
+ ,djvu
+ ,tiff)
+ callback))
+
+(defun doc-view-pdf->png-converter-mupdf (pdf png page callback)
+ (doc-view-start-process
+ "pdf->png" doc-view-pdfdraw-program
+ `(,(concat "-o" png)
+ ,(format "-r%d" (round doc-view-resolution))
+ ,pdf
+ ,@(if page `(,(format "%d" page))))
+ 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
callback))
(defun doc-view-pdf/ps->png (pdf-ps png)
+ ;; FIXME: Fix name and docstring to account for djvu&tiff.
"Convert PDF-PS to PNG asynchronously."
- (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))
+ (funcall
+ (pcase doc-view-doc-type
+ (`pdf doc-view-pdf->png-converter-function)
+ (`djvu #'doc-view-djvu->tiff-converter-ddjvu)
+ (_ #'doc-view-ps->png-converter-ghostscript))
+ pdf-ps png nil
(let ((resolution doc-view-resolution))
(lambda ()
;; Only create the resolution file when it's all done, so it also
(cancel-timer doc-view-current-timer)
(setq doc-view-current-timer nil))
(doc-view-display (current-buffer) 'force))))
+
;; Update the displayed pages as soon as they're done generating.
(when doc-view-conversion-refresh-interval
(setq doc-view-current-timer
'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))
-
(declare-function clear-image-cache "image.c" (&optional filter))
-(defun doc-view-pdf->png (pdf png pages)
- "Convert a PDF file to PNG asynchronously.
+(defun doc-view-document->bitmap (pdf png pages)
+ "Convert a document file to bitmap images asynchronously.
Start by converting PAGES, and then the rest."
(if (null pages)
(doc-view-pdf/ps->png pdf png)
;; a single page anyway, and of the remaining 1%, few cases will have
;; consecutive pages, it's not worth the trouble.
(let ((rest (cdr pages)))
- (doc-view-pdf->png-1
- pdf (format png (car pages)) (car pages)
+ (funcall doc-view-single-page-converter-function
+ pdf (format png (car pages)) (car pages)
(lambda ()
(if rest
- (doc-view-pdf->png pdf png rest)
+ (doc-view-document->bitmap pdf png rest)
;; Yippie, the important pages are done, update the display.
(clear-image-cache)
;; For the windows that have a message (like "Welcome to
;; 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)))))))
(list "-raw" pdf txt)
callback))
+(defun doc-view-current-cache-doc-pdf ()
+ "Return the name of the doc.pdf in the current cache dir.
+ This file exists only if the current document isn't a PDF or PS file already."
+ (expand-file-name "doc.pdf" (doc-view-current-cache-dir)))
+
(defun doc-view-doc->txt (txt callback)
"Convert the current document to text and call CALLBACK when done."
(make-directory (doc-view-current-cache-dir) t)
(`ps
;; Doc is a PS, so convert it to PDF (which will be converted to
;; TXT thereafter).
- (let ((pdf (expand-file-name "doc.pdf"
- (doc-view-current-cache-dir))))
+ (let ((pdf (doc-view-current-cache-doc-pdf)))
(doc-view-ps->pdf doc-view-buffer-file-name pdf
(lambda () (doc-view-pdf->txt pdf txt callback)))))
(`dvi
;; 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))
+ (doc-view-pdf->txt (doc-view-current-cache-doc-pdf) 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))
+ (doc-view-pdf->txt (doc-view-current-cache-doc-pdf) txt callback))
(_ (error "DocView doesn't know what to do"))))
(defun doc-view-ps->pdf (ps pdf callback)
;; 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))))
+ (let ((png-file (expand-file-name
+ (format doc-view--image-file-pattern "%d")
+ (doc-view-current-cache-dir))))
(make-directory (doc-view-current-cache-dir) t)
(pcase doc-view-doc-type
(`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)))
+ (let ((pdf (doc-view-current-cache-doc-pdf)))
(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.
- (let ((pdf (expand-file-name "doc.pdf" doc-view-current-cache-dir))
- (opdf (expand-file-name (concat (file-name-base doc-view-buffer-file-name)
- ".pdf")
- doc-view-current-cache-dir))
+ (let ((pdf (doc-view-current-cache-doc-pdf))
+ (opdf (expand-file-name
+ (concat (file-name-base doc-view-buffer-file-name)
+ ".pdf")
+ doc-view-current-cache-dir))
(png-file png-file))
- ;; The unoconv tool only supports a output directory, but no
+ ;; The unoconv tool only supports an 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
;; Rename to doc.pdf
(rename-file opdf pdf)
(doc-view-pdf/ps->png pdf png-file)))))
- (`pdf
+ ((or `pdf `djvu)
(let ((pages (doc-view-active-pages)))
- ;; Convert PDF to PNG images starting with the active pages.
- (doc-view-pdf->png doc-view-buffer-file-name png-file pages)))
+ ;; Convert doc to bitmap images starting with the active pages.
+ (doc-view-document->bitmap doc-view-buffer-file-name png-file pages)))
(_
;; Convert to PNG images.
(doc-view-pdf/ps->png doc-view-buffer-file-name png-file)))))
margins of the document. It prompts for the top-left corner (X
and Y) of the slice to display and its WIDTH and HEIGHT.
-See `doc-view-set-slice-using-mouse' for a more convenient way to
-do that. To reset the slice use `doc-view-reset-slice'."
+See `doc-view-set-slice-using-mouse' and
+`doc-view-set-slice-from-bounding-box' for more convenient ways
+to do that. To reset the slice use `doc-view-reset-slice'."
(interactive
(let* ((size (image-size (doc-view-current-image) t))
(a (read-number (format "Top-left X (0..%d): " (car size))))
(setq done t))))
(doc-view-set-slice x y w h)))
+(defun doc-view-get-bounding-box ()
+ "Get the BoundingBox information of the current page."
+ (let* ((page (doc-view-current-page))
+ (doc (let ((cache-doc (doc-view-current-cache-doc-pdf)))
+ (if (file-exists-p cache-doc)
+ cache-doc
+ doc-view-buffer-file-name)))
+ (o (shell-command-to-string
+ (concat doc-view-ghostscript-program
+ " -dSAFER -dBATCH -dNOPAUSE -q -sDEVICE=bbox "
+ (format "-dFirstPage=%s -dLastPage=%s %s"
+ page page doc)))))
+ (save-match-data
+ (when (string-match (concat "%%BoundingBox: "
+ "\\([[:digit:]]+\\) \\([[:digit:]]+\\) "
+ "\\([[:digit:]]+\\) \\([[:digit:]]+\\)") o)
+ (mapcar #'string-to-number
+ (list (match-string 1 o)
+ (match-string 2 o)
+ (match-string 3 o)
+ (match-string 4 o)))))))
+
+(defvar doc-view-paper-sizes
+ '((a4 595 842)
+ (a4-landscape 842 595)
+ (letter 612 792)
+ (letter-landscape 792 612)
+ (legal 612 1008)
+ (legal-landscape 1008 612)
+ (a3 842 1191)
+ (a3-landscape 1191 842)
+ (tabloid 792 1224)
+ (ledger 1224 792))
+ "An alist from paper size names to dimensions.")
+
+(defun doc-view-guess-paper-size (iw ih)
+ "Guess the paper size according to the aspect ratio."
+ (cl-labels ((div (x y)
+ (round (/ (* 100.0 x) y))))
+ (let ((ar (div iw ih))
+ (al (mapcar (lambda (l)
+ (list (div (nth 1 l) (nth 2 l)) (car l)))
+ doc-view-paper-sizes)))
+ (cadr (assoc ar al)))))
+
+(defun doc-view-scale-bounding-box (ps iw ih bb)
+ (list (/ (* (nth 0 bb) iw) (nth 1 (assoc ps doc-view-paper-sizes)))
+ (/ (* (nth 1 bb) ih) (nth 2 (assoc ps doc-view-paper-sizes)))
+ (/ (* (nth 2 bb) iw) (nth 1 (assoc ps doc-view-paper-sizes)))
+ (/ (* (nth 3 bb) ih) (nth 2 (assoc ps doc-view-paper-sizes)))))
+
+(defun doc-view-set-slice-from-bounding-box (&optional force-paper-size)
+ "Set the slice from the document's BoundingBox information.
+The result is that the margins are almost completely cropped,
+much more accurate than could be done manually using
+`doc-view-set-slice-using-mouse'."
+ (interactive "P")
+ (let ((bb (doc-view-get-bounding-box)))
+ (if (not bb)
+ (message "BoundingBox couldn't be determined")
+ (let* ((is (image-size (doc-view-current-image) t))
+ (iw (car is))
+ (ih (cdr is))
+ (ps (or (and (null force-paper-size)
+ (doc-view-guess-paper-size iw ih))
+ (intern (completing-read "Paper size: "
+ doc-view-paper-sizes
+ nil t))))
+ (bb (doc-view-scale-bounding-box ps iw ih bb))
+ (x1 (nth 0 bb))
+ (y1 (nth 1 bb))
+ (x2 (nth 2 bb))
+ (y2 (nth 3 bb)))
+ ;; We keep a 2 pixel margin.
+ (doc-view-set-slice (- x1 2) (- ih y2 2)
+ (+ (- x2 x1) 4) (+ (- y2 y1) 4))))))
+
(defun doc-view-reset-slice ()
"Reset the current slice.
After calling this function whole pages will be visible again."
(setq doc-view-pending-cache-flush nil))
(let ((ol (doc-view-current-overlay))
(image (if (and file (file-readable-p file))
- (if (not (fboundp 'imagemagick-types))
- (apply 'create-image file 'png nil args)
+ (if (not (and doc-view-scale-internally
+ (fboundp 'imagemagick-types)))
+ (apply 'create-image file doc-view--image-type nil args)
(unless (member :width args)
- (setq args (append args (list :width doc-view-image-width))))
+ (setq args `(,@args :width ,doc-view-image-width)))
(apply 'create-image file 'imagemagick nil args))))
(slice (doc-view-current-slice)))
(setf (doc-view-current-image) image)
(let ((prev-pages doc-view-current-files))
(setq doc-view-current-files
(sort (directory-files (doc-view-current-cache-dir) t
- "page-[0-9]+\\.png" t)
+ (format doc-view--image-file-pattern
+ "[0-9]+")
+ t)
'doc-view-sort))
+ (unless (eq (length prev-pages) (length doc-view-current-files))
+ (force-mode-line-update))
(dolist (win (or (get-buffer-window-list buffer nil t)
- (list (selected-window))))
+ (list t)))
(let* ((page (doc-view-current-page win))
- (pagefile (expand-file-name (format "page-%d.png" page)
- (doc-view-current-cache-dir))))
+ (pagefile (expand-file-name
+ (format doc-view--image-file-pattern page)
+ (doc-view-current-cache-dir))))
(when (or force
(and (not (member pagefile prev-pages))
(member pagefile doc-view-current-files)))
- (with-selected-window win
- (cl-assert (eq (current-buffer) buffer))
- (doc-view-goto-page page))))))))
+ (if (windowp win)
+ (with-selected-window win
+ (cl-assert (eq (current-buffer) buffer) t)
+ (doc-view-goto-page page))
+ (doc-view-goto-page page))))))))
(defun doc-view-buffer-message ()
;; Only show this message initially, not when refreshing the buffer (in which
;;;;; Toggle between editing and viewing
+(defvar-local doc-view-saved-settings nil
+ "Doc-view settings saved while in some other mode.")
+(put 'doc-view-saved-settings 'permanent-local t)
+
(defun doc-view-toggle-display ()
"Toggle between editing a document as text or viewing it."
(interactive)
(doc-view-kill-proc)
(setq buffer-read-only nil)
(remove-overlays (point-min) (point-max) 'doc-view t)
- (set (make-local-variable 'image-mode-winprops-alist) t)
+ (setq-local image-mode-winprops-alist t)
;; Switch to the previously used major mode or fall back to
;; normal mode.
(doc-view-fallback-mode)
;; the conversion is incomplete.
(file-readable-p (expand-file-name "resolution.el"
(doc-view-current-cache-dir)))
- (> (length (directory-files (doc-view-current-cache-dir)
- nil "\\.png\\'"))
+ (> (length (directory-files
+ (doc-view-current-cache-dir)
+ nil (format doc-view--image-file-pattern "[0-9]+")))
0)))
(defun doc-view-initiate-display ()
- ;; Switch to image display if possible
+ ;; Switch to image display if possible.
(if (doc-view-mode-p doc-view-doc-type)
(progn
(doc-view-buffer-message)
(if (doc-view-already-converted-p)
(progn
(message "DocView: using cached files!")
- ;; Load the saved resolution
+ ;; Load the saved resolution.
(let* ((res-file (expand-file-name "resolution.el"
(doc-view-current-cache-dir)))
(res
(insert-file-contents res-file)
(read (current-buffer))))))
(when (numberp res)
- (set (make-local-variable 'doc-view-resolution) res)))
+ (setq-local doc-view-resolution res)))
(doc-view-display (current-buffer) 'force))
(doc-view-convert-current-doc))
(message
("pdf" pdf) ("epdf" pdf)
;; PostScript
("ps" ps) ("eps" ps)
+ ;; DjVu
+ ("djvu" djvu)
;; OpenDocument formats
("odt" odf) ("ods" odf) ("odp" odf) ("odg" odf)
("odc" odf) ("odi" odf) ("odm" odf) ("ott" odf)
(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"))))))
+ ((looking-at "\367\002") '(dvi))
+ ((looking-at "AT&TFORM") '(djvu))))))
+ (setq-local 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"))))))
+
+(defun doc-view-set-up-single-converter ()
+ "Find the right single-page converter for the current document type"
+ (pcase-let ((`(,conv-function ,type ,extension)
+ (pcase doc-view-doc-type
+ (`djvu (list #'doc-view-djvu->tiff-converter-ddjvu 'tiff "tif"))
+ (_ (list doc-view-pdf->png-converter-function 'png "png")))))
+ (setq-local doc-view-single-page-converter-function conv-function)
+ (setq-local doc-view--image-type type)
+ (setq-local doc-view--image-file-pattern (concat "page-%s." extension))))
;;;###autoload
(defun doc-view-mode ()
;; returns nil for tar members.
(doc-view-fallback-mode)
- (let* ((prev-major-mode (if (eq major-mode 'doc-view-mode)
+ (let* ((prev-major-mode (if (derived-mode-p 'doc-view-mode)
doc-view-previous-major-mode
- (when (not (memq major-mode
- '(doc-view-mode fundamental-mode)))
+ (unless (eq major-mode 'fundamental-mode)
major-mode))))
(kill-all-local-variables)
- (set (make-local-variable 'doc-view-previous-major-mode) prev-major-mode))
+ (setq-local doc-view-previous-major-mode prev-major-mode))
+
+ (dolist (var doc-view-saved-settings)
+ (set (make-local-variable (car var)) (cdr var)))
;; Figure out the document type.
(unless doc-view-doc-type
(doc-view-set-doc-type))
+ (doc-view-set-up-single-converter)
(doc-view-make-safe-dir doc-view-cache-directory)
;; Handle compressed files, remote files, files inside archives
- (set (make-local-variable 'doc-view-buffer-file-name)
+ (setq-local doc-view-buffer-file-name
(cond
(jka-compr-really-do-compress
;; FIXME: there's a risk of name conflicts here.
'doc-view-new-window-function nil t)
(image-mode-setup-winprops)
- (set (make-local-variable 'mode-line-position)
- '(" P" (:eval (number-to-string (doc-view-current-page)))
- "/" (:eval (number-to-string (doc-view-last-page-number)))))
+ (setq-local mode-line-position
+ '(" P" (:eval (number-to-string (doc-view-current-page)))
+ "/" (:eval (number-to-string (doc-view-last-page-number)))))
;; Don't scroll unless the user specifically asked for it.
- (set (make-local-variable 'auto-hscroll-mode) nil)
- (set (make-local-variable 'mwheel-scroll-up-function)
- 'doc-view-scroll-up-or-next-page)
- (set (make-local-variable 'mwheel-scroll-down-function)
- 'doc-view-scroll-down-or-previous-page)
- (set (make-local-variable 'cursor-type) nil)
+ (setq-local auto-hscroll-mode nil)
+ (setq-local mwheel-scroll-up-function #'doc-view-scroll-up-or-next-page)
+ (setq-local mwheel-scroll-down-function
+ #'doc-view-scroll-down-or-previous-page)
+ (setq-local cursor-type nil)
(use-local-map doc-view-mode-map)
- (set (make-local-variable 'after-revert-hook) 'doc-view-reconvert-doc)
- (set (make-local-variable 'bookmark-make-record-function)
- 'doc-view-bookmark-make-record)
+ (add-hook 'after-revert-hook 'doc-view-reconvert-doc nil t)
+ (setq-local bookmark-make-record-function
+ #'doc-view-bookmark-make-record)
(setq mode-name "DocView"
buffer-read-only t
major-mode 'doc-view-mode)
;; canonical view mode for PDF/PS/DVI files. This could be
;; switched on automatically depending on the value of
;; `view-read-only'.
- (set (make-local-variable 'view-read-only) nil)
+ (setq-local 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))))
+ (let ((vars (if (derived-mode-p 'doc-view-mode)
+ (mapcar (lambda (var) (cons var (symbol-value var)))
+ '(doc-view-resolution
+ image-mode-winprops-alist)))))
+ (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)))
+ (when vars
+ (setq-local doc-view-saved-settings vars))))
;;;###autoload
(defun doc-view-mode-maybe ()