Subject: Re: bug#62713: 29.0.60; DocView shows wrong conversion indicator when configured to use svg images

From: Visuwesh <[email-masked]>
Date: Mon, 25 Sep 2023 18:17:01 +0530

[திங்கள் செப்டம்பர் 25, 2023] Visuwesh wrote:

> It is better solved by writing a separate pdf->svg converter function.
> Right now, the svg conversion is tacked on top of pdf->png converter
> function used for MuPDF.  This was done because I thought only MuPDF
> supported pdf->svg conversion but later I was informed that poppler has
> utilities to do too.

And looks like the poppler utility pdftocairo does not work the doc-view
expects it to when generating svg images.  AFAICT, there is no way to
make pdftocairo to generate a svg image per page like other tools do.  I
cannot loop either as there is also no way to know the number of pages
beforehand.  The png function also has hacks since pdftocairo is not
cooperative, and changing doc-view to not use
doc-view--image-file-pattern does not seem to be easy.  I attach the
patch named "doc-view-general-svg-wip.patch" which has my WIP and it
works just fine for MuPDF.

This particular problem can be easily solved by the following simple
patch instead:

diff --git a/lisp/doc-view.el b/lisp/doc-view.el
index b856b09c8b7..c0d27bdc68a 100644
--- a/lisp/doc-view.el
+++ b/lisp/doc-view.el
@@ -1283,7 +1283,8 @@ 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->" (symbol-name doc-view--image-type))
+     doc-view-pdfdraw-program
      `(,@(doc-view-pdfdraw-program-subcommand)
        ,@options
        ,pdf

But if you say we don't bother about pdftocairo support and implement
the generic support system, I can prepare a patch without the pdftocairo
stuff, and add some documentation to aid the users to write custom
converter functions.

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)