From 0015d84bea1204b4534e5568ff7c0920b9ef02b7 Mon Sep 17 00:00:00 2001 From: Nick Bowler Date: Mon, 22 Feb 2021 20:29:38 -0500 Subject: [PATCH] Embed SVG icons directly into output. Inlining SVGs directly in the markup seems to interoperate somewhat better than linking SVGs in img elements, and we can fairly easily create fallback markup that degrades to a raster image. Currently this is only applicable to the file listings because the fallback markup is hardcoded to the icons on these pages. This will need to be expanded in the future. The results seem pretty good, although nothing I do in old Mozilla versions seems to be able to get the stylesheet sizes to properly apply to SVG elements, so the icons are rendered correctly but at the wrong scale. SVG support can be disabled in this browser and the fallback works properly. More work may be required. --- Rules | 8 +- content/{images => icons}/down.svg | 0 content/{images => icons}/folder.svg | 0 content/{images => icons}/return.svg | 0 content/{images => icons}/up.svg | 0 content/style.scss | 54 +++--- layouts/clickytable.xsl | 4 +- layouts/default.xsl | 6 +- layouts/embed-svg.xsl | 251 +++++++++++++++++++++++++++ layouts/functions.xsl | 15 +- layouts/listing.erb | 6 +- lib/css-clean-selectors.rb | 5 + lib/svg2png.rb | 35 ++++ lib/xhtml-compat.rb | 42 ++++- 14 files changed, 391 insertions(+), 35 deletions(-) rename content/{images => icons}/down.svg (100%) rename content/{images => icons}/folder.svg (100%) rename content/{images => icons}/return.svg (100%) rename content/{images => icons}/up.svg (100%) create mode 100644 layouts/embed-svg.xsl create mode 100644 lib/svg2png.rb diff --git a/Rules b/Rules index ebe9cf2..d3247cb 100644 --- a/Rules +++ b/Rules @@ -132,8 +132,9 @@ compile '/**/index.lst' do layout '/listing.erb' layout '/default.xml' layout '/default.xsl' + layout '/embed-svg.xsl' filter :relativize_paths, type: :xml - filter :xhtml_compat + filter :xhtml_compat, fix_doctype: true write item.identifier.without_ext + ".xhtml" end @@ -209,6 +210,11 @@ compile '/**/*.svg' do write @item.identifier.to_s end +compile '/icons/**/*.svg', rep: :icon32 do + filter :svg2png, width: 32, height: 32 + write @item.identifier.without_ext + "-32.png" +end + compile '/**/*' do filter :copybin if @item.binary? write @item.identifier.to_s diff --git a/content/images/down.svg b/content/icons/down.svg similarity index 100% rename from content/images/down.svg rename to content/icons/down.svg diff --git a/content/images/folder.svg b/content/icons/folder.svg similarity index 100% rename from content/images/folder.svg rename to content/icons/folder.svg diff --git a/content/images/return.svg b/content/icons/return.svg similarity index 100% rename from content/images/return.svg rename to content/icons/return.svg diff --git a/content/images/up.svg b/content/icons/up.svg similarity index 100% rename from content/images/up.svg rename to content/icons/up.svg diff --git a/content/style.scss b/content/style.scss index 5240779..912fe4a 100644 --- a/content/style.scss +++ b/content/style.scss @@ -240,7 +240,7 @@ $clickynames: name, date, size; } } - &:focus ~ table th.clicky-#{$col}>label~label>span { + &:focus ~ table th.clicky-#{$col}>label~label>span:first-child { border-color: $foregroundcolour; } @@ -256,15 +256,15 @@ $clickynames: name, date, size; &:checked ~ table { // Update table header state & th.clicky-#{$col} { - img+img { + .svg+.svg { display: -moz-inline-box !important; display: inline-block !important; } - img { display: none; } + .svg { display: none; } } } - &:focus ~ table th.clicky-#{$col}>label~label>img { + &:focus ~ table th.clicky-#{$col}>label~label .svg { border-color: $foregroundcolour; } @@ -275,16 +275,17 @@ $clickynames: name, date, size; } th.clicky-#{$col}>label { - &, &>* { - white-space: nowrap; - vertical-align: middle; + white-space: nowrap; + cursor: pointer; + line-height: 1.5em; + + &>* { display: -moz-inline-box; display: inline-block; - cursor: pointer; + border: 1px dotted transparent; + vertical-align: middle; } - &>* { border: 1px dotted transparent; } - // Expand the first label a bit so the table (hopefully) // does not reshape as columns are selected. &:first-child { @@ -293,28 +294,39 @@ $clickynames: name, date, size; } &:active { color: $linkactivecolour; } - &:first-child:active>span, &~label:active>img { + &:first-child:active>span, &~label:active>.svg { border-color: $linkactivecolour; } - img { + .svg { margin-left: 0.25em; - width: 1.5em; - height: auto; } + + .svg, svg, img.svgfallback { + height: 1.5em; + width: auto; + } + .svg svg { width: 1.5em; } } } table.filelist { - &>tr>*:first-child, &>*>tr>*:first-child { - &+td { min-width: 50%; } - width: 0; + &>*>tr>*:first-child { + &+* { width: 50%; } + // chrome doesn't like width: 0 for some reason + width: 0.1px; } - tbody img { - display: block; - height: 1.5em; - width: auto; + tbody { + .svg, svg, img.svgfallback { + height: 1.5em; + width: auto; + } + + .svg { + svg { width: 1.5em; } + display: inline-block; + } } } diff --git a/layouts/clickytable.xsl b/layouts/clickytable.xsl index f442fa4..4858b7f 100644 --- a/layouts/clickytable.xsl +++ b/layouts/clickytable.xsl @@ -86,8 +86,8 @@ diff --git a/layouts/default.xsl b/layouts/default.xsl index 22f67d6..f0fddc9 100644 --- a/layouts/default.xsl +++ b/layouts/default.xsl @@ -27,11 +27,7 @@ exclude-result-prefixes='xhtml'> - - + diff --git a/layouts/embed-svg.xsl b/layouts/embed-svg.xsl new file mode 100644 index 0000000..2b3f8e2 --- /dev/null +++ b/layouts/embed-svg.xsl @@ -0,0 +1,251 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -32.png + + svgfallback + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + by + + + + + . + + + , + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + url( + + ) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/layouts/functions.xsl b/layouts/functions.xsl index d9c5748..a88c338 100644 --- a/layouts/functions.xsl +++ b/layouts/functions.xsl @@ -1,7 +1,7 @@ + + @@ -67,6 +72,14 @@ or f:element-is-span($node)' /> + + + + + + + diff --git a/layouts/listing.erb b/layouts/listing.erb index 02017a0..2db5f35 100644 --- a/layouts/listing.erb +++ b/layouts/listing.erb @@ -41,7 +41,7 @@ files = {} files[f] = { sorttime: if t then t.to_f else 0.0 end, - displaytime: if t then t.getutc.strftime "%Y-%m-%d %H:%M UTC" end, + displaytime: if t then t.getutc.strftime "%Y-%m-%d %H:%M\u00a0UTC" end, displaysize: displaysize, size: size, type: type, @@ -58,8 +58,8 @@ def render_entry(files, key) return <<~EOF #{if f[:type] "#{f[:type]}" end} diff --git a/lib/css-clean-selectors.rb b/lib/css-clean-selectors.rb index 7eebab7..f6c6ad6 100644 --- a/lib/css-clean-selectors.rb +++ b/lib/css-clean-selectors.rb @@ -42,6 +42,11 @@ class CssCleanSelectorsFilter < Nanoc::Filter next if ts[i].nil? or ts[i][:node] != :delim next if ts[i][:value] == '*' + if ts[i-1] + # keep whitespace before class selectors + next if ts[i][:value] == '.' and ts[i-1][:node] == :whitespace + end + ts[i-1] = nil if ts[i-1] and ts[i-1][:node] == :whitespace ts[i+1] = nil if ts[i+1] and ts[i+1][:node] == :whitespace end diff --git a/lib/svg2png.rb b/lib/svg2png.rb new file mode 100644 index 0000000..6147f2b --- /dev/null +++ b/lib/svg2png.rb @@ -0,0 +1,35 @@ +# Nick's web site: svg2png filter: convert SVG to PNG using rsvg-convert. +# +# Copyright © 2021 Nick Bowler +# +# This program 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. +# +# This program 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 this program. If not, see . + +class SVG2PNG < Nanoc::Filter + identifier :svg2png + type :text => :binary + + def run(content, params = {}) + args = ["-o", output_filename] + params.each do |key, val| + next if !val + + args << "--#{key.to_s.gsub("_", "-")}" + if val != true then args << val.to_s end + end + + dummy, status = Open3.capture2("rsvg-convert", *args, + stdin_data: content) + raise "rsvg-convert failed" if status != 0 + end +end diff --git a/lib/xhtml-compat.rb b/lib/xhtml-compat.rb index 1c738d9..c110502 100644 --- a/lib/xhtml-compat.rb +++ b/lib/xhtml-compat.rb @@ -18,11 +18,47 @@ class XhtmlCompatFilter < Nanoc::Filter identifier :xhtml_compat + requires 'nokogiri' + + Xmlns = { + math: 'http://www.w3.org/1998/Math/MathML', + svg: 'http://www.w3.org/2000/svg', + }.freeze + + XHTMLPublic = '-//W3C//DTD XHTML 1.1//EN' + MathPublic = '-//W3C//DTD XHTML 1.1 plus MathML 2.0//EN' + MathSystem = 'http://www.w3.org/Math/DTD/mathml2/xhtml-math11-f.dtd' + SVGPublic = '-//W3C//DTD XHTML 1.1 plus MathML 2.0 plus SVG 1.1//EN' + SVGSystem = 'http://www.w3.org/2002/04/xhtml-math-svg/xhtml-math-svg.dtd' + + # XSLT 1.0 as implemented in Nokogiri canot construct doctypes based + # on content. When using MathML or SVG elements in XHTML a different + # doctype is needed: select one based on which elements are present. + def fix_doctype(content, params = {}) + return "#{content}" if not params[:fix_doctype] + + doc = Nokogiri::XML(content) + doctype = doc.internal_subset + + return "#{content}" if doctype.external_id != XHTMLPublic + + if not doc.xpath("//svg:svg", Xmlns).empty? + doctype.remove + doc.create_internal_subset("html", SVGPublic, SVGSystem) + elsif not doc.xpath("//math:math", Xmlns).empty? + doctype.remove + doc.create_internal_subset("html", MathPublic, MathSystem) + end + + return doc.to_xml + end def run(content, params = {}) + text = fix_doctype(content, params) + # Old versions of Netscape get confused by
but have no problem # with
, so avoid that by adding spaces to such elements. - text = content.gsub(/([^[:space:]])\/>/m, '\1 />'); + text.gsub!(/([^[:space:]])\/>/m, '\1 />'); # Even older versions of Netscape interpret any script as Javascript, # which causes major problems with the CDATA hack; solve that by making @@ -31,6 +67,8 @@ class XhtmlCompatFilter < Nanoc::Filter text.gsub!("]]>", '\&*/') # Delete any zero-width word joiners added for XSLT processing. - return text.delete "\u2060" + text.delete! "\u2060" + + return text end end -- 2.43.0