diff --git a/lisp/doc-view.el b/lisp/doc-view.el index b856b09c8b7..03b01de5033 100644 --- a/lisp/doc-view.el +++ b/lisp/doc-view.el @@ -193,28 +193,63 @@ doc-view-pdfdraw-program :type 'file :version "24.4") +(defcustom doc-view-poppler-program "pdftocairo" + "Name of pdfcairo program from Poppler." + :type 'file + :version "30.1") + (defcustom doc-view-pdftotext-program-args '("-raw") "Parameters to give to the pdftotext command." :version "27.1" :type '(repeat string)) (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) + (cond + ((executable-find doc-view-poppler-program) + #'doc-view-pdf->png-converter-poppler) + ((executable-find doc-view-pdfdraw-program) + #'doc-view-pdf->png-converter-mupdf) + (t #'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-item doc-view-pdf->png-converter-poppler + :doc "Use pdfcairo from poppler") function) :version "24.4") +(defcustom doc-view-pdf->svg-converter-function + (cond + ((executable-find doc-view-poppler-program) + #'doc-view-pdf->svg-converter-poppler) + ((executable-find doc-view-pdfdraw-program) + #'doc-view-pdf->svg-converter-mupdf) + (t #'doc-view-pdf->svg-converter-mupdf)) + "Function to call to convert a PDF file into a SVG file." + :type '(radio + (function-item doc-view-pdf->svg-converter-mupdf + :doc "Use mupdf") + (function-item doc-view-pdf->svg-converter-poppler + :doc "Use pdfcairo from poppler") + function) + :version "30.1") + +(defcustom doc-view-use-svg (image-type-available-p 'svg) + "Whether to use svg images for files whenever possible." + :type 'boolean + :version "30.1") + (defcustom doc-view-mupdf-use-svg (image-type-available-p 'svg) "Whether to use svg images for PDF files." :type 'boolean - :version "30.1") + :version "30.1" + :set (lambda (sym val) + (set-default-toplevel-value sym val) + (setq doc-view-use-svg val))) +(make-obsolete 'doc-view-mupdf-use-svg 'doc-view-use-svg "30.1") (defcustom doc-view-imenu-enabled (and (executable-find "mutool") t) "Whether to generate an imenu outline when \"mutool\" is available." @@ -241,7 +276,7 @@ doc-view-imenu-flatten (defface doc-view-svg-face '((t :inherit default)) "Face used for SVG images. Only background and foreground colors are used. -See `doc-view-mupdf-use-svg'." +See `doc-view-use-svg'." :version "30.1") (make-obsolete 'doc-view-svg-background 'doc-view-svg-face "30.1") @@ -1266,10 +1301,10 @@ doc-view-pdf-password-protected-pdfdraw-p (goto-char (point-min)) (search-forward "error: cannot authenticate password" nil t))) -(defun doc-view-pdf->png-converter-mupdf (pdf png page callback) +(defun doc-view-pdf->img-converter-mupdf (pdf type img page callback) (let* ((pdf-passwd (if (doc-view-pdf-password-protected-pdfdraw-p pdf) (read-passwd "Enter password for PDF file: "))) - (options `(,(concat "-o" png) + (options `(,(concat "-o" img) ,(format "-r%d" (round doc-view-resolution)) ,@(if pdf-passwd `("-p" ,pdf-passwd))))) (when (eq doc-view-doc-type 'epub) @@ -1283,13 +1318,74 @@ doc-view-pdf->png-converter-mupdf (expand-file-name doc-view-epub-user-stylesheet))))))) (doc-view-start-process - "pdf->png" doc-view-pdfdraw-program + (concat "pdf->" type) doc-view-pdfdraw-program `(,@(doc-view-pdfdraw-program-subcommand) ,@options ,pdf ,@(if page `(,(format "%d" page)))) callback))) +(defun doc-view-pdf->png-converter-mupdf (pdf img page callback) + (doc-view-pdf->img-converter-mupdf pdf "png" img page callback)) + +(defun doc-view-pdf->svg-converter-mupdf (pdf img page callback) + (doc-view-pdf->img-converter-mupdf pdf "svg" img page callback)) + +(defun doc-view-pdf-password-protected-poppler-p (pdf) + "Return non-nil if a PDF file is password-protected." + (with-temp-buffer + (let* ((tmp (make-temp-name "emacs-doc-view-test")) + (abs (expand-file-name (concat tmp ".png") temporary-file-directory))) + (call-process doc-view-poppler-program nil (current-buffer) nil + "-png" "-f" "1" "-singlefile" pdf abs) + (when (file-regular-p abs) + (delete-file abs)) + (goto-char (point-min)) + (search-forward "Incorrect password" nil t)))) + +(defun doc-view-pdf->png-converter-poppler (pdf imgfile page callback) + (let ((passwd (when (doc-view-pdf-password-protected-poppler-p pdf) + (read-passwd "Enter password for PDF file: ")))) + ;; HACK: pdftocairo doesn't accept format arguments in the image + ;; filename argument for PNG, JPEG, TIFF, and doc-view relies on a + ;; valid `doc-view--image-file-pattern' that can be passed to + ;; `format' to change pages in `doc-view-goto-page'. But + ;; thankfully, when PAGE is nil it means to fetch all pages. + (when (and (null page) (string-match-p "%d" imgfile)) + (setq imgfile (string-remove-suffix + (concat "-%d." (symbol-name doc-view--image-type)) + imgfile))) + (doc-view-start-process + "pdf->png" + doc-view-poppler-program + `("-png" "-r" ,(format "%d" (round doc-view-resolution)) + ,@(when passwd `("-opw" ,passwd)) + ,pdf + ,@(if page ; Single page. + (list "-f" (format "%d" page) "-singlefile" + (file-name-sans-extension imgfile)) + (list imgfile))) + callback))) + +(defun doc-view-pdf->svg-converter-poppler (pdf imgfile page callback) + (let ((passwd (when (doc-view-pdf-password-protected-poppler-p pdf) + (read-passwd "Enter password for PDF file: ")))) + ;; For vector formats such as svg, output-file name is handled + ;; differently. *Sigh* + ;; There seems to be no way to generate separate image per-page so + ;; we give up? + (unless (null page) + (doc-view-start-process + "pdf->svg" + doc-view-poppler-program + `("-svg" "-r" ,(format "%d" (round doc-view-resolution)) + ,@(when passwd `("-opw" ,passwd)) + ,pdf + ;; -singlefile is not supported for svg... + "-f" ,(format "%d" page) "-l" ,(format "%d" page) + ,imgfile) + callback)))) + (defun doc-view-odf->pdf-converter-unoconv (odf callback) "Convert ODF to PDF asynchronously and call CALLBACK when finished. The converted PDF is put into the current cache directory, and it @@ -1329,7 +1425,9 @@ doc-view-pdf/ps->png (funcall (pcase doc-view-doc-type ((or 'pdf 'odf 'epub 'cbz 'fb2 'xps 'oxps) - doc-view-pdf->png-converter-function) + (if doc-view-use-svg + doc-view-pdf->svg-converter-function + doc-view-pdf->png-converter-function)) ('djvu #'doc-view-djvu->tiff-converter-ddjvu) (_ #'doc-view-ps->png-converter-ghostscript)) pdf-ps png nil @@ -2148,10 +2246,8 @@ doc-view-set-up-single-converter (pcase-let ((`(,conv-function ,type ,extension) (pcase doc-view-doc-type ('djvu (list #'doc-view-djvu->tiff-converter-ddjvu 'tiff "tif")) - (_ (if (and (eq doc-view-pdf->png-converter-function - #'doc-view-pdf->png-converter-mupdf) - doc-view-mupdf-use-svg) - (list doc-view-pdf->png-converter-function 'svg "svg") + (_ (if doc-view-use-svg + (list doc-view-pdf->svg-converter-function 'svg "svg") (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)