;;; ox-svg4css.el --- HTML Derived Backend -*- lexical-binding: t; -*-
;; Copyright (C) 2023 Free Software Foundation, Inc.
;; Author: Gerard Vermeulen
;; Maintainer: Gerard Vermeulen
;; Keywords: org, hypermedia
;; Since this library contains a lot of code from the Emacs ox-html
;; library, see the ox-html library for its authors, maintainer and
;; FSF copyright.
;; This file is NOT part of GNU Emacs.
;; GNU Emacs is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; GNU Emacs is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs. If not, see .
;;; Commentary:
;; This library implements a HTML derived backend for Org export.
;; It tries to address limitations of SVG images and CSS in HTML pages:
;; 1. Firstly, it probes whether to export the SVG image as an "object" tag.
;; 2. Secondly, it probes whether to include the SVG contents in the HTML.
;; 3. Thirdly, it probes wheter to embed the SVG image in an "img" tag.
;; 4. The option precedence order is "svg-as-object", "svg-inclusion",
;; "org-html-inline-images", but disabling the latter disables the other
;; options.
;;
;; I prefer the "svg-as-object" option over the "svg-inclusion" option.
;;
;; See: https://list.orgmode.org/c1eef10be815748d2103cb81bce08708@posteo.net/
;; where Cristian Moe has proposed to export SVG as "object" tag.
;; Ihor Radchenko and Max Nikulin have proposed the idea of using special
;; "#+ATTR_HTML:" attributes to control the export options.
;;; Code:
(require 'org-macs)
(org-assert-version)
(require 'cl-lib)
(require 'ox-html)
;;; Define Backend
(org-export-define-derived-backend 'svg4css 'html
:menu-entry
'(?h 1
((?M "As SVG4CSS buffer" org-svg4css-export-as-html)
(?m "As SVG4CSS file" org-svg4css-export-to-html)
(?O "As SVG4CSS file and open"
(lambda (a s v b)
(if a (org-svg4css-export-to-html t s v b)
(org-open-file (org-svg4css-export-to-html nil s v b)))))))
:options-alist
'((:html-svg-as-object nil "svg-as-object" org-html-svg-as-object)
(:html-svg-inclusion nil "svg-inclusion" org-html-svg-inclusion))
:translate-alist
'((link . org-svg4css-link)))
;;; User Configuration Variables
(defcustom org-html-svg-as-object nil
"Non-nil means export SVG images in object tags when in-lining
applies, otherwise try SVG image inclusion or try to apply the
normal export rules.
SVG images in object tags and CSS links in such images simply work.
Note: https://www.w3schools.com/tags/tag_object.asp prefers \"img\"
tag usage."
:group 'org-export-html
:type 'boolean)
(defcustom org-html-svg-inclusion nil
"Non-nil means include SVG image contents in when in-lining
applies, otherwise try to apply the normal export rules.
The HTML file including the SVG image contents has to link to the
CSS to let the SVG image and the CSS work together.
Note: SVG inclusion breaks \"13.9.9 Images in HTML export\"."
:group 'org-export-html
:type 'boolean)
(defcustom org-html-no-attribute-names '("svg-as-object" "svg-inclusion")
"List of HTML tag attribute names to exclude from the tags.
Those attributes control image scope export options."
:group 'org-export-html
:type '(repeat string))
;;; Internal Functions
(defun org-svg4css--make-attribute-string (attributes no-attribute-names)
"Return a list of attributes, as a string.
ATTRIBUTES is a plist where values are either strings or nil. An
attribute with a nil value will be omitted from the result.
NO-ATTRIBUTE-NAMES lists attribute names to omit from the result."
(let (output)
(dolist (item attributes (mapconcat 'identity (nreverse output) " "))
(cond ((null item) (pop output))
((symbolp item) (push (substring (symbol-name item) 1) output))
(t (let ((key (car output))
(value (replace-regexp-in-string
"\"" """ (org-html-encode-plain-text item))))
(if (member key no-attribute-names)
(pop output)
(setcar output (format "%s=\"%s\"" key value)))))))))
(defun org-svg4css--svg-as-object-p (link info attributes-plist)
(and (plist-get info :html-inline-images)
(or (and (plist-get attributes-plist :svg-as-object))
(and (plist-get info :html-svg-as-object)
(or (not (plist-member attributes-plist :svg-as-object))
(plist-get attributes-plist :svg-as-object))))
(org-export-inline-image-p
link (plist-get info :html-inline-image-rules))
(not (org-element-contents link))
(let ((case-fold-search t))
(string-match-p ".svg\\'" (org-element-property :path link)))))
(defun org-svg4css--svg-inclusion-p (link info attributes-plist)
(and (plist-get info :html-inline-images)
(or (and (plist-get attributes-plist :svg-inclusion))
(and (plist-get info :html-svg-inclusion)
(or (not (plist-member attributes-plist :svg-inclusion))
(plist-get attributes-plist :svg-inclusion))))
(org-export-inline-image-p
link (plist-get info :html-inline-image-rules))
(not (org-element-contents link))
(let ((case-fold-search t))
(string-match-p ".svg\\'" (org-element-property :path link)))))
(defun org-svg4css--format-svg-as-object (path attributes-plist)
(format "" (org-svg4css--make-attribute-string
(org-combine-plists
(list :data path
:type "image/svg+xml")
attributes-plist)
org-html-no-attribute-names)))
(defun org-svg4css--format-svg-inclusion (path)
"Return the SVG contents of the file named PATH for inclusion."
(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