emacs-orgmode@gnu.org archives
 help / color / mirror / code / Atom feed
* [PATCH] ox-html.el: add option to embed SVG for CSS support in SVG
@ 2023-06-05 13:23 gerard.vermeulen
  2023-06-06  7:49 ` Ihor Radchenko
  2023-06-06 14:14 ` Max Nikulin
  0 siblings, 2 replies; 28+ messages in thread
From: gerard.vermeulen @ 2023-06-05 13:23 UTC (permalink / raw)
  To: Emacs orgmode

[-- Attachment #1: Type: text/plain, Size: 1793 bytes --]

Hi,

I have been trying to export SVG images having links to CSS from Org to 
HTML
and I have found that the this CSS is not active in Firefox (only 
browser I tried).

I have found that the CCS is only active under two conditions:
1. The HTML page should manage the CSS that the SVG image links to.
2. The SVG image should be embedded in the HTML page.

I wrote an ox-html derived export backend to embed SVG images:
https://forge.chapril.org/gav451/emacs.d/src/branch/main/site-lisp/ox-my-html/ox-my-html.el
and merged the SVG embedding functionality into ox-html.el leading to 
the
attached patch.

I have included an MWE below and I attached mwe.org and mwe.html with
doc8.svg and style8.svg (showing a flower and chosen for the visual 
impact).

You will get an M-non-WE after exporting mwe.org using ox-html on the
main branch.

# begin MWE
#+title: SVG and CSS MWE with patch or with ox-my-html backend
#+HTML_DOCTYPE: html5
#+HTML_HEAD: <link rel="stylesheet" type="text/css" href="style8.css" />
#+OPTIONS: ^:{} toc:nil html-embed-svg:nil
# The next line overrules html-embed-svg:t
#+HTML_EMBED_SVG_EXCLUDES: ./doc8.svg
# The next line overrules html-embed-svg:nil
#+HTML_EMBED_SVG_INCLUDES: ./doc8.svg

[[https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/SVG_and_CSS][SVG 
and CSS (MDN)]] is the origin of the files =doc8.svg= and
=style8.css=.  Open =doc8.svg= and =mwe.html= in Firefox to compare
hovering over the flower.  The flower is fully black in case of an
M-non-WE or in case of HTML export without patch or without ox-my-html
backend.

#+CAPTION: Is MWE with SVG embedding *and* with HTML_HEAD managing
#+CAPTION: the CSS. Becomes an M-non-WE without SVG embedding or
#+CAPTION: without HTML_HEAD managing the CSS.
[[./doc8.svg]]
# end MWE

Best regards -- Gerard

[-- Attachment #2: 0001-ox-html.el-add-option-to-embed-SVG-for-CSS-support-i.patch --]
[-- Type: application/octet-stream, Size: 6333 bytes --]

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


[-- Attachment #3: mwe.org --]
[-- Type: application/octet-stream, Size: 974 bytes --]

#+title: SVG and CSS MWE with patch or with ox-my-html backend
#+HTML_DOCTYPE: html5
#+HTML_HEAD: <link rel="stylesheet" type="text/css" href="style8.css" />
#+OPTIONS: ^:{} toc:nil html-embed-svg:nil
# The next line overrules html-embed-svg:t
#+HTML_EMBED_SVG_EXCLUDES: ./doc8.svg
# The next line overrules html-embed-svg:nil
#+HTML_EMBED_SVG_INCLUDES: ./doc8.svg

[[https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/SVG_and_CSS][SVG and CSS (MDN)]] is the origin of the files =doc8.svg= and
=style8.css=.  Open =doc8.svg= and =mwe.html= in Firefox to compare
hovering over the flower.  The flower is fully black in case of an
M-non-WE or in case of HTML export without patch or without ox-my-html
backend.

#+CAPTION: Is MWE with SVG embedding *and* with HTML_HEAD managing
#+CAPTION: the CSS. Becomes an M-non-WE without SVG embedding or
#+CAPTION: without HTML_HEAD managing the CSS.
[[./doc8.svg]]
# Note: ./doc8.svg starts with ./ to resolve it when inlining.

[-- Attachment #4: mwe.html --]
[-- Type: text/html, Size: 18497 bytes --]

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #5: style8.css --]
[-- Type: text/css; name=style8.css, Size: 2391 bytes --]

/*** SVG demonstration ***/

/* page */
svg {
  background-color: beige;
}

#heading {
  font-size: 24px;
  font-weight: bold;
}

#caption {
  font-size: 12px;
}

/* flower */
#flower:hover {
  cursor: crosshair;
}

/* gradient */
#fade-stop-1 {
  stop-color: blue;
}

#fade-stop-2 {
  stop-color: white;
}

/* petals */
.segment-fill {
  fill: var(--segment-fill-fill);
  stroke: var(--segment-fill-stroke);
  stroke-width: var(--segment-fill-stroke-width);
}

.segment-fill:hover {
  fill: var(--segment-fill-fill-hover);
  stroke: var(--segment-fill-stroke-hover);
}

.segment-edge {
  fill: var(--segment-edge-fill);
  stroke: var(--segment-edge-stroke);
  stroke-width: var(--segment-edge-stroke-width);
}

.segment-edge:hover {
  stroke: var(--segment-edge-stroke-hover);
}

/* outer petals */
#outer-petals {
  opacity: 0.75;
  --segment-fill-fill: azure;
  --segment-fill-stroke: lightsteelblue;
  --segment-fill-stroke-width: 1;
  --segment-edge-fill: none;
  --segment-edge-stroke: deepskyblue;
  --segment-edge-stroke-width: 3;
  --segment-fill-fill-hover: plum;
  --segment-fill-stroke-hover: none;
  --segment-edge-stroke-hover: slateblue;
}

/*
 Non-standard way of styling elements referenced via <use> elements,
 supported by some older browsers
*/
#outer-petals .segment-fill {
  fill: azure;
  stroke: lightsteelblue;
  stroke-width: 1;
}

#outer-petals .segment-edge {
  fill: none;
  stroke: deepskyblue;
  stroke-width: 3;
}

#outer-petals .segment:hover > .segment-fill {
  fill: plum;
  stroke: none;
}

#outer-petals .segment:hover > .segment-edge {
  stroke: slateblue;
}

/* inner petals */
#inner-petals {
  --segment-fill-fill: yellow;
  --segment-fill-stroke: yellow;
  --segment-fill-stroke-width: 1;
  --segment-edge-fill: none;
  --segment-edge-stroke: yellowgreen;
  --segment-edge-stroke-width: 9;
  --segment-fill-fill-hover: darkseagreen;
  --segment-fill-stroke-hover: none;
  --segment-edge-stroke-hover: green;
}

/*
 Non-standard way of styling elements referenced via <use> elements,
 supported by some older browsers
*/
#inner-petals .segment-fill {
  fill: yellow;
  stroke: yellow;
  stroke-width: 1;
}

#inner-petals .segment-edge {
  fill: none;
  stroke: yellowgreen;
  stroke-width: 9;
}

#inner-petals .segment:hover > .segment-fill {
  fill: darkseagreen;
  stroke: none;
}

#inner-petals .segment:hover > .segment-edge {
  stroke: green;
}

[-- Attachment #6: doc8.svg --]
[-- Type: image/svg+xml, Size: 9840 bytes --]

^ permalink raw reply related	[flat|nested] 28+ messages in thread

end of thread, other threads:[~2023-06-23 10:57 UTC | newest]

Thread overview: 28+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-06-05 13:23 [PATCH] ox-html.el: add option to embed SVG for CSS support in SVG gerard.vermeulen
2023-06-06  7:49 ` Ihor Radchenko
2023-06-06  9:57   ` Christian Moe
2023-06-06 13:37     ` gerard.vermeulen
2023-06-06 18:54       ` Christian Moe
2023-06-07  8:43         ` Ihor Radchenko
2023-06-07  8:41     ` Ihor Radchenko
2023-06-07 14:30       ` Christian Moe
2023-06-07 17:57         ` Ihor Radchenko
2023-06-15 13:09           ` gerard.vermeulen
2023-06-15 14:55             ` Max Nikulin
2023-06-15 20:42               ` Ihor Radchenko
2023-06-15 20:51             ` Ihor Radchenko
2023-06-16 18:39               ` gerard.vermeulen
2023-06-17 12:38                 ` Ihor Radchenko
2023-06-17 14:45                   ` gerard.vermeulen
2023-06-21 15:52               ` Max Nikulin
2023-06-21 16:02                 ` Ihor Radchenko
2023-06-21 16:27                   ` Max Nikulin
2023-06-21 16:38                     ` Ihor Radchenko
2023-06-22 16:25                       ` Max Nikulin
2023-06-23 11:02                         ` Ihor Radchenko
2023-06-06 12:58   ` gerard.vermeulen
2023-06-07  9:07     ` Ihor Radchenko
2023-06-06 14:14 ` Max Nikulin
2023-06-06 16:08   ` gerard.vermeulen
2023-06-07  9:10     ` Ihor Radchenko
2023-06-09 15:27     ` Max Nikulin

Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs/org-mode.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).