From a8d9e61016d455ebcbf878e3c9d6a4531dd21dc6 Mon Sep 17 00:00:00 2001
From: Gerard Vermeulen <gerard.vermeulen@posteo.net>
Date: Mon, 5 Jun 2023 11:13:36 +0200
Subject: [PATCH] ox-html.el: add option to embed SVG for CSS support in SVG

* lisp/ox-html (org-html-embed-svg, org-html-embed-svg-includes,
  org-html-embed-html-excludes): add option to enable or disable
  SVG embedding in HTML within emacs of file scope. Add options
  to specify lists of SVG images to be included in or excluded
  from embedding.
  (org-html--embed-svg-p): apply the logic to each SVG image.
  (org-html-svg-contents): try to return cleaned up SVG contents.
  (org-html-link): add embedding SVG images before inlining images.
---
 lisp/ox-html.el | 78 +++++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 75 insertions(+), 3 deletions(-)

diff --git a/lisp/ox-html.el b/lisp/ox-html.el
index b519402b1..938b977ac 100644
--- a/lisp/ox-html.el
+++ b/lisp/ox-html.el
@@ -24,7 +24,7 @@
 
 ;;; Commentary:
 
-;; This library implements a HTML backend for Org generic exporter.
+;; This library implements an HTML backend for Org generic exporter.
 ;; See Org manual for more information.
 
 ;;; Code:
@@ -134,6 +134,10 @@
     (:html-head "HTML_HEAD" nil org-html-head newline)
     (:html-head-extra "HTML_HEAD_EXTRA" nil org-html-head-extra newline)
     (:subtitle "SUBTITLE" nil nil parse)
+    (:html-embed-svg-excludes "HTML_EMBED_SVG_EXCLUDES" nil
+                              org-html-embed-svg-excludes split)
+    (:html-embed-svg-includes "HTML_EMBED_SVG_INCLUDES" nil
+                              org-html-embed-svg-includes split)
     (:html-head-include-default-style
      nil "html-style" org-html-head-include-default-style)
     (:html-head-include-scripts nil "html-scripts" org-html-head-include-scripts)
@@ -187,6 +191,7 @@
     (:html-klipse-js nil nil org-html-klipse-js)
     (:html-klipse-selection-script nil nil org-html-klipse-selection-script)
     (:infojs-opt "INFOJS_OPT" nil nil)
+    (:with-html-svg-embedding nil "html-embed-svg" org-html-embed-svg)
     ;; Redefine regular options.
     (:creator "CREATOR" nil org-html-creator-string)
     (:with-latex nil "tex" org-html-with-latex)
@@ -849,6 +854,43 @@ When nil, the links still point to the plain \".org\" file."
   :group 'org-export-html
   :type 'boolean)
 
+;;;; Links :: Embed SVG
+
+(defcustom org-html-embed-svg nil
+  "Non-nil means embed SVG images into exported HTML pages,
+otherwise link to SVG images from exported HTML pages.
+
+This option can also be set in Org files with
+#+OPTIONS: html-embed-svg:t
+or
+#+OPTIONS: html-embed-svg:nil
+to enable or disable SVG embedding in exported HTML."
+  :group 'org-export-html
+  :version "30.0"
+  :type 'boolean)
+
+(defcustom org-html-embed-svg-excludes nil
+  "List of SVG files to exclude from SVG embedding.
+
+This option overrules an `org-html-embed-svg' non-nil value.
+
+It can also be set with the HTML_EMBED_SVG_EXCLUDES keyword."
+  :group 'org-export-html
+  :version "30.0"
+  :type '(repeat string)
+  :safe (lambda (x) (and (listp x) (cl-every #'stringp x))))
+
+(defcustom org-html-embed-svg-includes nil
+  "List of SVG files to include in SVG embedding.
+
+This option overrules an `org-html-embed-svg' nil value.
+
+It can also be set with the HTML_EMBED_SVG_INCLUDES keyword."
+  :group 'org-export-html
+  :version "30.0"
+  :type '(repeat string)
+  :safe (lambda (x) (and (listp x) (cl-every #'stringp x))))
+
 ;;;; Links :: Inline images
 
 (defcustom org-html-inline-images t
@@ -2279,7 +2321,7 @@ INFO is a plist used as a communication channel."
 ;;;; Anchor
 
 (defun org-html--anchor (id desc attributes info)
-  "Format a HTML anchor."
+  "Format an HTML anchor."
   (let* ((name (and (plist-get info :html-allow-name-attribute-in-anchors) id))
 	 (attributes (concat (and id (format " id=\"%s\"" id))
 			     (and name (format " name=\"%s\"" name))
@@ -3122,6 +3164,33 @@ CONTENTS is nil.  INFO is a plist holding contextual information."
 
 ;;;; Link
 
+(defun org-html--embed-svg-p (link path info)
+  "Check whether LINK and INFO specify to embed the SVG file named PATH.
+LINK must have no contents and link to an SVG file.  INFO may contain
+lists of SVG files to include in and/or to exclude from embedding."
+  (and (not (org-element-contents link))
+       (let ((case-fold-search t))
+         (string-match-p ".svg\\'" (org-element-property :path link)))
+       (or (and (plist-get info :with-html-svg-embedding)
+                (not (member path (plist-get info :html-embed-svg-excludes))))
+           (and (not (plist-get info :with-html-svg-embedding))
+                (member path (plist-get info :html-embed-svg-includes))))))
+
+(defun org-html-svg-contents (path)
+  "Return the SVG contents of the file named PATH."
+  (with-temp-buffer
+    (insert-file-contents path)
+    ;; Delete text preceding something starting as an SVG root element.
+    ;; The intent is to remove XML declarations (and XML comments).
+    ;; This breaks in case of a preceding XML comment with <svg inside
+    ;; or a preceding XML element with an SVG element inside.
+    ;; See https://emacs.stackexchange.com/a/57433 for the original code.
+    (let ((case-fold-search t))
+      (unless (search-forward "<svg" nil 'noerror)
+        (user-error "Can't find a root SVG start tag in file `%s'." path)))
+    (delete-region (point-min) (match-beginning 0))
+    (buffer-string)))
+
 (defun org-html-image-link-filter (data _backend info)
   (org-export-insert-image-links data info org-html-inline-image-rules))
 
@@ -3266,6 +3335,9 @@ INFO is a plist holding contextual information.  See
     (cond
      ;; Link type is handled by a special function.
      ((org-export-custom-protocol-maybe link desc 'html info))
+     ;; Embed SVG.
+     ((org-html--embed-svg-p link path info)
+      (org-html-svg-contents path))
      ;; Image file.
      ((and (plist-get info :html-inline-images)
 	   (org-export-inline-image-p
@@ -3972,7 +4044,7 @@ to convert it."
 ;;;###autoload
 (defun org-html-export-to-html
     (&optional async subtreep visible-only body-only ext-plist)
-  "Export current buffer to a HTML file.
+  "Export current buffer to an HTML file.
 
 If narrowing is active in the current buffer, only export its
 narrowed part.
-- 
2.41.0

