]> git.draconx.ca Git - homepage.git/blob - layouts/embed-svg.xsl
Make archive file icon shadows visible in dark mode.
[homepage.git] / layouts / embed-svg.xsl
1 <?xml version='1.0' encoding='UTF-8' ?>
2 <!--
3   Nick's web site: SVG embedding.
4
5   Copyright © 2021-2022 Nick Bowler
6
7   This program is free software: you can redistribute it and/or modify
8   it under the terms of the GNU General Public License as published by
9   the Free Software Foundation, either version 3 of the License, or
10   (at your option) any later version.
11
12   This program is distributed in the hope that it will be useful,
13   but WITHOUT ANY WARRANTY; without even the implied warranty of
14   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15   GNU General Public License for more details.
16
17   You should have received a copy of the GNU General Public License
18   along with this program.  If not, see <https://www.gnu.org/licenses/>
19 -->
20 <xsl:stylesheet version='1.0'
21   xmlns='http://www.w3.org/1999/xhtml'
22   xmlns:cc='http://creativecommons.org/ns#'
23   xmlns:dc='http://purl.org/dc/elements/1.1/'
24   xmlns:f='http://draconx.ca/my-functions'
25   xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'
26   xmlns:svg="http://www.w3.org/2000/svg"
27   xmlns:xhtml='http://www.w3.org/1999/xhtml'
28   xmlns:xlink='http://www.w3.org/1999/xlink'
29   xmlns:xsl='http://www.w3.org/1999/XSL/Transform'
30   extension-element-prefixes="f"
31   exclude-result-prefixes="cc dc rdf xhtml">
32
33 <xsl:import href='layouts/functions.xsl' />
34 <xsl:output cdata-section-elements='style script' />
35 <xsl:strip-space elements='xhtml:html xhtml:body' />
36
37 <xsl:key name='embed-svg' match='//xhtml:img[f:ends-with(@src, ".svg")]'
38   use='@src' />
39
40 <xsl:key name='id' match='//*[@id]' use='@id' />
41
42 <xsl:template match='node()|@*'>
43   <xsl:copy>
44     <xsl:apply-templates select='node()|@*' />
45   </xsl:copy>
46 </xsl:template>
47
48 <!-- force a literal result to ensure html gets all namespace nodes -->
49 <xsl:template name='rewrite-html'>
50   <html>
51     <xsl:apply-templates select='node()|@*' />
52   </html>
53 </xsl:template>
54
55 <xsl:template match='/xhtml:html'>
56   <xsl:choose>
57     <xsl:when test='//xhtml:img[key("embed-svg", @src)]'>
58       <xsl:call-template name='rewrite-html' />
59     </xsl:when>
60     <xsl:otherwise>
61       <xsl:element name='{name()}' namespace='{namespace-uri()}'>
62         <xsl:apply-templates select='node()|@*' />
63       </xsl:element>
64     </xsl:otherwise>
65   </xsl:choose>
66 </xsl:template>
67
68 <xsl:template match='xhtml:img[key("embed-svg", @src)]'>
69   <span class='svg'>
70     <xsl:apply-templates select='@style' />
71     <svg:svg>
72       <xsl:apply-templates select='@width|@height' />
73       <svg:switch>
74         <svg:use xlink:href='#es-{generate-id(key("embed-svg", @src))}' />
75         <svg:foreignObject width='0' height='0'>
76           <!-- TODO: this hardcoded fallback method needs to be more flexible -->
77           <xsl:copy>
78             <xsl:attribute name='src'>
79               <xsl:value-of select='substring-before(@src, ".svg")' />
80               <xsl:text>-32.png</xsl:text>
81             </xsl:attribute>
82             <xsl:attribute name='class'>svgfallback</xsl:attribute>
83             <xsl:apply-templates select='node()|
84                   @*[not(contains("src style class", local-name()))]' />
85           </xsl:copy>
86         </svg:foreignObject>
87       </svg:switch>
88     </svg:svg>
89   </span>
90 </xsl:template>
91
92 <!-- SVG embedding -->
93 <xsl:template mode='embed-svg' match='node()|@*'>
94   <xsl:copy>
95     <xsl:apply-templates select='node()|@*' />
96   </xsl:copy>
97 </xsl:template>
98
99 <!-- Remove all whitespace-only text nodes from embedded SVG -->
100 <xsl:template mode='embed-svg' match='text()[normalize-space(.) = ""]' />
101
102 <xsl:template name='svgnode' mode='embed-svg' match='svg:*'>
103   <xsl:param name='idnode' select='.' />
104
105   <!-- doctype demands svg: prefix -->
106   <xsl:element name='svg:{local-name()}' namespace='{namespace-uri()}'>
107     <!-- rewrite all id nodes to avoid conflicts with other embeddings -->
108     <xsl:if test='@id or not(parent::*)'>
109       <xsl:attribute name='id'>
110         <xsl:value-of select='concat("es-", generate-id($idnode))' />
111       </xsl:attribute>
112     </xsl:if>
113     <xsl:choose>
114       <xsl:when test='$idnode/@src and not(parent::*)'>
115         <!-- remove .svg suffix -->
116         <xsl:variable name='raw'
117           select='substring($idnode/@src, 1, string-length($idnode/@src)-4)' />
118
119         <xsl:attribute name='class'>
120           <xsl:value-of select='normalize-space(concat(@class, " embed ",
121             translate($raw, "/", " ")))' />
122         </xsl:attribute>
123       </xsl:when>
124       <xsl:otherwise>
125         <xsl:apply-templates mode='embed-svg' select='@class' />
126       </xsl:otherwise>
127     </xsl:if>
128     <xsl:apply-templates mode='embed-svg'
129       select='@*[local-name()!="id" and local-name()!="class"]' />
130     <xsl:apply-templates mode='embed-svg' select='node()'>
131       <xsl:sort select='-count(self::svg:metadata)' data-type='number' />
132     </xsl:apply-templates>
133   </xsl:element>
134 </xsl:template>
135
136 <xsl:template name='depx'>
137   <xsl:param name='node' select='.' />
138   <xsl:choose>
139     <xsl:when test='contains($node, "px")'>
140       <xsl:value-of select='substring-before($node, "px")' />
141     </xsl:when>
142     <xsl:otherwise>
143       <xsl:value-of select='node' />
144     </xsl:otherwise>
145   </xsl:choose>
146 </xsl:template>
147
148 <xsl:template mode='embed-svg' match='/svg:svg/@width[../@height]'>
149   <xsl:attribute name='viewBox'>
150     <xsl:text>0 0 </xsl:text>
151     <xsl:call-template name='depx' />
152     <xsl:text> </xsl:text>
153     <xsl:call-template name='depx'>
154       <xsl:with-param name='node' select='../@height' />
155     </xsl:call-template>
156   </xsl:attribute>
157 </xsl:template>
158 <xsl:template mode='embed-svg' match='/svg:svg/@height[../@width]' />
159
160 <!--
161   the RDF stuff is disallowed by doctype, try and transform to an
162   attribution comment.
163 -->
164 <xsl:template mode='embed-svg' match='svg:metadata'>
165   <xsl:if test='descendant::cc:Work'>
166     <xsl:comment>
167       <xsl:apply-templates select='descendant::cc:Work' />
168     </xsl:comment>
169   </xsl:if>
170 </xsl:template>
171
172 <!-- hackjob to stringify the work info -->
173 <xsl:template match='cc:Work'>
174   <xsl:text>
175   </xsl:text>
176   <xsl:if test='dc:title'>
177     <xsl:value-of select='concat("“", dc:title, "”")' />
178   </xsl:if>
179   <xsl:if test='dc:creator/cc:Agent'>
180     <xsl:text> by </xsl:text>
181     <xsl:for-each select='dc:creator/cc:Agent'>
182       <xsl:value-of select='dc:title' />
183       <xsl:choose>
184         <xsl:when test='position()=last()'>
185           <xsl:text>.</xsl:text>
186         </xsl:when>
187         <xsl:otherwise>
188           <xsl:text>, </xsl:text>
189         </xsl:otherwise>
190       </xsl:choose>
191     </xsl:for-each>
192   </xsl:if>
193   <xsl:if test='dc:source'>
194     <xsl:text>
195   </xsl:text>
196     <xsl:value-of select='dc:source' />
197   </xsl:if>
198   <xsl:if test='cc:license/@rdf:resource'>
199     <xsl:text>
200   </xsl:text>
201     <xsl:value-of select='cc:license/@rdf:resource' />
202   </xsl:if>
203   <xsl:text>
204 </xsl:text>
205 </xsl:template>
206
207 <!-- match all xlink:href attributes to generated IDs for this document -->
208 <xsl:template mode='embed-svg' match='@xlink:href[starts-with(.,"#")]'>
209   <xsl:variable name='ref' select='substring-after(., "#")' />
210
211   <xsl:attribute name='xlink:href'>
212     <xsl:value-of select='concat("#es-", generate-id(key("id", $ref)))' />
213   </xsl:attribute>
214 </xsl:template>
215
216 <!-- rewrite all attributes containing url(#id) to generated IDs -->
217 <xsl:template name='rewrite-urls'>
218   <xsl:param name='val' select='.' />
219
220   <xsl:choose>
221     <xsl:when test='contains($val, "url(#")'>
222       <xsl:variable name='tail' select='substring-after($val, "url(#")' />
223       <xsl:variable name='ref' select='substring-before($tail, ")")' />
224
225       <xsl:value-of select='substring-before($val, "url(#")' />
226       <xsl:text>url(</xsl:text>
227       <xsl:value-of select='concat("#es-", generate-id(key("id", $ref)))' />
228       <xsl:text>)</xsl:text>
229       <xsl:call-template name='rewrite-urls'>
230         <xsl:with-param name='val' select='substring-after($tail, ")")' />
231       </xsl:call-template>
232     </xsl:when>
233     <xsl:otherwise>
234       <xsl:value-of select='$val' />
235     </xsl:otherwise>
236   </xsl:choose>
237 </xsl:template>
238
239 <xsl:template mode='embed-svg' match='@*[contains(., "url(#")]'>
240   <xsl:attribute name='{name()}'>
241     <xsl:call-template name='rewrite-urls' />
242   </xsl:attribute>
243 </xsl:template>
244
245 <xsl:template match='xhtml:body'>
246   <xsl:copy>
247     <xsl:apply-templates select='node()|@*' />
248     <xsl:if test='key("embed-svg", //xhtml:img/@src)'>
249       <script type='x'><![CDATA[]]x><!--]]></script>
250       <svg:svg width='0' height='0'>
251         <svg:defs>
252           <xsl:for-each select='//xhtml:img'>
253             <xsl:if test='generate-id(.)=generate-id(key("embed-svg", @src))'>
254               <xsl:apply-templates mode='embed-svg'
255                 select='document(concat("output", @src))'>
256                 <xsl:with-param name='idnode' select='.' />
257               </xsl:apply-templates>
258             </xsl:if>
259           </xsl:for-each>
260         </svg:defs>
261       </svg:svg>
262       <script type='x'>--></script>
263     </xsl:if>
264   </xsl:copy>
265 </xsl:template>
266
267 </xsl:stylesheet>