# Find output reps corresponding to this key, if any
item.reps.each do |rep|
+ next if rep.raw_path.nil?
next unless
FileUtils.identical?(item.raw_filename, rep.raw_path)
end
compile '/images/*.jpg', rep: :large do
+ w, h = FastImage.size(item.raw_filename)
filename = item.identifier.without_ext + '-t1200.' + item.identifier.ext
- filter :imgresize, width: 1200, height: 1200, cache: filename
+ filter :imgresize, width: [w, 1200].min, height: [h, 1200].min, cache: filename
write filename
end
+compile '/images/*.jpg', rep: :medium do
+ w, h = FastImage.size(item.raw_filename)
+ filename = item.identifier.without_ext + '-t800.' + item.identifier.ext
+ if w > 900 or h > 900
+ filter :imgresize, width: 800, height: 800, cache: filename
+ write filename
+ end
+end
+
compile '/images/*.jpg', rep: :info do
filter :imginfo
layout '/imginfo.xsl'
--- /dev/null
+../../.git/annex/objects/jf/z5/SHA512-s257188--05f166f1b15bb984ed150ea4a87e369f58dccdb3a561ae0ec2975da1e194bffc4da0a36dc14d2c6d881bc1424d1eff66241701291bab666f2a95beac66412415/SHA512-s257188--05f166f1b15bb984ed150ea4a87e369f58dccdb3a561ae0ec2975da1e194bffc4da0a36dc14d2c6d881bc1424d1eff66241701291bab666f2a95beac66412415
\ No newline at end of file
--- /dev/null
+---
+title: 2021-06-10 Partial Eclipse Through Clouds
+copyright: 2021 Nick Bowler
+license: cc-by-sa-4.0
+description: |
+ Almost 20 minutes after the maximum eclipse, the clouds begin to come back
+ to rejoin the fun.
--- /dev/null
+../../.git/annex/objects/00/F8/SHA512-s107816--4e990963d97828f1d68bca567d8f5ebd95cbca92106d7b185c3fe32d0bb7ee0f402273f1854929181526c98b0c0a83f8c6befb63bf4855be39e617814c70f38c/SHA512-s107816--4e990963d97828f1d68bca567d8f5ebd95cbca92106d7b185c3fe32d0bb7ee0f402273f1854929181526c98b0c0a83f8c6befb63bf4855be39e617814c70f38c
\ No newline at end of file
--- /dev/null
+---
+title: 2021-06-10 Fare Thee Well, Eclipse
+copyright: 2021 Nick Bowler
+license: cc-by-sa-4.0
+description: |
+ The spectacle is mostly over as the sun returns to hiding behind some clouds.
--- /dev/null
+../../.git/annex/objects/Qv/71/SHA512-s456326--212c721a3ee49a09a1b7f55fd3a7db962cec291f2e1c9274b954e029bf41451476d5aef545596af7f1bce45132541a7a9760357bdc7b03ff50a8bf83d40b9d80/SHA512-s456326--212c721a3ee49a09a1b7f55fd3a7db962cec291f2e1c9274b954e029bf41451476d5aef545596af7f1bce45132541a7a9760357bdc7b03ff50a8bf83d40b9d80
\ No newline at end of file
--- /dev/null
+---
+title: 2021-06-10 Maximum Eclipse from Ottawa
+copyright: 2021 Nick Bowler
+license: cc-by-sa-4.0
+description: |
+ Greatest eclipse as seen from Ottawa on 2021-06-10, with approximately 80% of
+ the sun obscured.
--- /dev/null
+../../.git/annex/objects/V5/kz/SHA512-s55143--bb4418266b4e21b6a4b3f39f7aec3deb6593ce0fdaf3b7d60feca81e7398165315b69e989012e583352ad2d4960f890099bd570f39d7e286b14a07bcf6a70534/SHA512-s55143--bb4418266b4e21b6a4b3f39f7aec3deb6593ce0fdaf3b7d60feca81e7398165315b69e989012e583352ad2d4960f890099bd570f39d7e286b14a07bcf6a70534
\ No newline at end of file
--- /dev/null
+---
+title: 2021-06-10 Sunrise from Ottawa
+copyright: 2021 Nick Bowler
+license: cc-by-sa-4.0
+description: |
+ The eclipse is well under way at sunrise. Some clouds obscure the horizon
+ but after about 10 minutes the sun begins to rise above them.
margin: 1em;
}
-a:link { color: $linkdefaultcolour; }
-a:visited { color: $linkvisitedcolour; }
-a:active { color: $linkactivecolour; }
+a:link { color: $linkdefaultcolour; border-color: $linkdefaultcolour; }
+a:visited { color: $linkvisitedcolour; border-color: $linkvisitedcolour; }
+a:active { color: $linkactivecolour; border-color: $linkactivecolour; }
h1 { @include header_size(60em, 2em); }
h2 { @include header_size(60em, 1.5em); }
h5 { @include header_size(60em, 1em); }
-p>img { max-width: 40em; width: 100%; height: auto; }
+p.img {
+ text-align: center;
+
+ img {
+ vertical-align: bottom;
+ max-width: 40em;
+ width: 100%;
+ height: auto;
+ }
+
+ a {
+ text-decoration: none;
+ display: inline-block;
+ border: solid 2px;
+ }
+
+ small {
+ color: $foregroundcolour;
+ text-align: justify;
+ @media (max-width: 24em) { text-align: left; }
+ padding: 0.5ex;
+ display: block;
+ font-size: 0.9em;
+ }
+}
p, dt, dd, li {
text-align: justify;
--- /dev/null
+---
+title: Solar Eclipse in Ottawa on 2021-06-10
+copyright: 2021 Nick Bowler
+license: cc-by-sa-4.0
+published: 2021-06-13T23:43:47-0400
+---
+
+On June 10, 2021, I tried to take some photos of the sun, but the moon
+kept getting in the way.
+
+<%= item_to_img(@items["/images/eclipse-20210610-sunrise.jpg"], caption: <<EOF
+Typical Ottawa: a perfectly nice day except for that one stupid cloud in the sky.
+EOF
+) %>
+<%= item_to_img(@items["/images/eclipse-20210610-max.jpg"], caption: <<EOF
+I told the cloud that I called by-law and will stand here until they come.
+EOF
+) %>
+<%= item_to_img(@items["/images/eclipse-20210610-clouds.jpg"], caption: <<EOF
+But the cloud came back, the very next hour.
+EOF
+) %>
+<%= item_to_img(@items["/images/eclipse-20210610-farewell.jpg"], caption: <<EOF
+I will follow this cloud home if I have to!
+EOF
+) %>
--- /dev/null
+<%
+s = ' ' * (@indent || 2)
+copyyears = {}
+copyauthors = {}
+[@item.fetch(:copyright, [])].flatten.each do |copyright| %>
+<%=s%><copyright><%= copyright %></copyright><%
+x = expand_copyright(copyright)
+x[:years].each do |y| copyyears[y] = 1 end
+copyauthors[x[:name]] = 1
+end
+copyyears.keys.sort.each do |y| %>
+<%=s%><copyright-year><%= y %></copyright-year><%
+end
+copyauthors.keys.sort.each do |i| %>
+<%=s%><copyright-holder><%= i %></copyright-holder><%
+end
+ if @item[:license] then
+ licref = @item[:license].split
+ lic = find_license(licref[0]) %>
+<%=s%><license>
+<%=s%> <identifier><%= File::basename(lic.identifier.without_ext) %></identifier>
+<%=s%> <shortname><%= license_shortname(lic) %></shortname>
+<%=s%> <name><%= [lic[:title], licref[1..-1]].compact.join(" ").strip %></name>
+<%=s%> <uri><%= item_uri(lic) %></uri>
+<%=s%> <modification-allowed><%=
+ lic[:"modification-allowed"] == true ? "yes" : "no"
+ %></modification-allowed>
+<%=s%></license><%
+end %>
doc_header = doc.xpath("string(//xhtml:h1)", Xmlns)
end
%><document>
- <title><%= @item.fetch(:title, doc_header) %></title>
-<% [@item.fetch(:copyright, [])].flatten.each do |copyright|
-%> <copyright><%= copyright %></copyright>
-<% end
- if @item[:license] then
- licref = @item[:license].split
- lic = find_license(licref[0])
-%> <license>
- <identifier><%= File::basename(lic.identifier.without_ext) %></identifier>
- <name><%= [lic[:title], licref[1..-1]].compact.join(" ").strip %></name>
- <uri><%= item_uri(lic) %></uri>
- <modification-allowed><%=
- lic[:"modification-allowed"] == true ? "yes" : "no"
- %></modification-allowed>
- </license>
-<% end
-%> <source>
+ <title><%= @item.fetch(:title, doc_header) %></title><%=
+render '/copyright.xml' %><%
+find_images.each do |item| %>
+ <image>
+ <title><%= item[:attrname] || item[:title] %></title>
+ <uri><%= item_uri(item, rep: :info) %></uri><%=
+render '/copyright.xml', :item => item, :indent => 4
+%> </image><%
+end %>
+ <source>
<% if @item.raw_filename %>
<file><%= item_source(@item) %></file>
<% elsif File.basename(@item.identifier) == "index.lst" %>
xmlns:xhtml='http://www.w3.org/1999/xhtml'
xmlns:xsl='http://www.w3.org/1999/XSL/Transform'
xmlns:func='http://exslt.org/functions'
+ xmlns:exslt='http://exslt.org/common'
xmlns:f='http://draconx.ca/my-functions'
- extension-element-prefixes='func f'
+ extension-element-prefixes='exslt func f'
exclude-result-prefixes='xhtml'>
<xsl:import href='layouts/functions.xsl' />
<xsl:copy><xsl:apply-templates select='node()|@*' /></xsl:copy>
</xsl:template>
+<xsl:template name='notransform' mode='notransform' match='node()|@*'>
+ <xsl:copy>
+ <xsl:apply-templates mode='notransform' select='node()|@*' />
+ </xsl:copy>
+</xsl:template>
+
<!--
Nokogiri's pretty-printer is a bit weird. Regardless of the indentation
setting, if an element has no child text nodes then it will be pretty-
</xsl:template>
<xsl:template match='license'>
+ <xsl:variable name='node' select='.' />
<p>
- <xsl:text>Copying and distribution of this material</xsl:text>
+ <xsl:choose>
+ <xsl:when test='/document/image[license != $node]'>
+ <xsl:text>Except as otherwise noted, copying</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>Copying</xsl:otherwise>
+ </xsl:choose>
+ <xsl:text> and distribution of this material</xsl:text>
<xsl:if test='normalize-space(modification-allowed)="yes"'>
<xsl:text>, with or without modification,</xsl:text>
</xsl:if>
</xsl:template>
<xsl:template name='image-attribution'>
-<!--
- <xsl:variable name='x' select='/document/image[copyright-holder="Nick Bowler"][1]' />
- <xsl:variable name='y' select='/document/image[copyright-holder="Nick Bowler"][4]' />
--->
<xsl:variable name='images-fragment'>
<xsl:for-each select='/document/image'>
<xsl:sort select='number(copyright-holder = /document/copyright-holder)'
</xsl:if>
</xsl:template>
+<xsl:template match='xhtml:p[count(*)=1 and normalize-space(text())=""
+ and descendant::xhtml:img]'>
+ <xsl:copy>
+ <xsl:apply-templates select='@*[local-name() != "class"]' />
+ <xsl:attribute name='class'>
+ <xsl:if test='@class'>
+ <xsl:value-of select='concat(@class, " ")' />
+ </xsl:if>
+ <xsl:text>img</xsl:text>
+ </xsl:attribute>
+ <xsl:apply-templates select='node()' />
+ </xsl:copy>
+</xsl:template>
+
<xsl:template match='/'>
<html>
<head>
<div id='footer'>
<xsl:apply-templates select='/document/copyright' />
<xsl:apply-templates select='/document/license' />
+ <xsl:if test='/document/image'>
+ <xsl:call-template name='image-attribution' />
+ </xsl:if>
<xsl:apply-templates select='/document/source' />
</div>
</body>
<xsl:copy><xsl:apply-templates select='node()|@*' /></xsl:copy>
</xsl:template>
-<xsl:template name='exif'>
+<xsl:template name='exif' mode='exif' match='*'>
<xsl:param name='node' select='.' />
<xsl:param name='name' select='local-name($node)' />
<td><xsl:value-of select='$node' /></td>
</tr>
</xsl:template>
+
<xsl:template match='exif'>
<h2>Metadata</h2>
<table>
<tr><th>Attribute</th><th>Value</th></tr>
</thead>
<tbody>
- <xsl:call-template name='exif'>
- <xsl:with-param name='node' select='make' />
+ <xsl:apply-templates select='make' mode='exif'>
<xsl:with-param name='name' select='"Camera make"' />
- </xsl:call-template>
- <xsl:call-template name='exif'>
- <xsl:with-param name='node' select='model' />
+ </xsl:apply-templates>
+ <xsl:apply-templates select='model' mode='exif'>
<xsl:with-param name='name' select='"Camera model"' />
- </xsl:call-template>
- <xsl:call-template name='exif'>
- <xsl:with-param name='node' select='date_time_original' />
+ </xsl:apply-templates>
+ <xsl:apply-templates select='date_time_original' mode='exif'>
<xsl:with-param name='name' select='"Date taken"' />
- </xsl:call-template>
- <xsl:call-template name='exif'>
+ </xsl:apply-templates>
+ <xsl:apply-templates select='exposure_time' mode='exif'>
<xsl:with-param name='node' select='concat(exposure_time, "s")' />
<xsl:with-param name='name' select='"Shutter speed"' />
- </xsl:call-template>
- <xsl:call-template name='exif'>
+ </xsl:apply-templates>
+ <xsl:apply-templates select='f_number' mode='exif'>
<xsl:with-param name='node' select='concat("f/", f_number)' />
<xsl:with-param name='name' select='"Aperture"' />
- </xsl:call-template>
- <xsl:call-template name='exif'>
- <xsl:with-param name='node' select='exposure_bias_value' />
+ </xsl:apply-templates>
+ <xsl:apply-templates select='exposure_bias_value' mode='exif'>
<xsl:with-param name='name' select='"Exposure compensation"' />
- </xsl:call-template>
- <xsl:call-template name='exif'>
+ </xsl:apply-templates>
+ <xsl:apply-templates select='focal_length' mode='exif'>
<xsl:with-param name='node' select='concat(focal_length, "mm")' />
<xsl:with-param name='name' select='"Focal length"' />
- </xsl:call-template>
- <xsl:call-template name='exif'>
- <xsl:with-param name='node' select='flash' />
+ </xsl:apply-templates>
+ <xsl:apply-templates select='flash' mode='exif'>
<xsl:with-param name='name' select='"Flash"' />
- </xsl:call-template>
- <xsl:call-template name='exif'>
- <xsl:with-param name='node' select='iso_speed_ratings' />
+ </xsl:apply-templates>
+ <xsl:apply-templates select='iso_speed_ratings' mode='exif'>
<xsl:with-param name='name' select='"ISO speed rating"' />
- </xsl:call-template>
- <xsl:call-template name='exif'>
- <xsl:with-param name='node' select='exposure_program' />
+ </xsl:apply-templates>
+ <xsl:apply-templates select='exposure_program' mode='exif'>
<xsl:with-param name='name' select='"Exposure program"' />
- </xsl:call-template>
- <xsl:call-template name='exif'>
- <xsl:with-param name='node' select='metering_mode' />
+ </xsl:apply-templates>
+ <xsl:apply-templates select='metering_mode' mode='exif'>
<xsl:with-param name='name' select='"Metering mode"' />
- </xsl:call-template>
+ </xsl:apply-templates>
</tbody>
</table>
</xsl:template>
# along with this program. If not, see <https://www.gnu.org/licenses/>.
require 'nokogiri'
+require 'fastimage'
-use_helper Nanoc::Helpers::Breadcrumbs
use_helper Nanoc::Helpers::Blogging
+use_helper Nanoc::Helpers::Breadcrumbs
+use_helper Nanoc::Helpers::Rendering
Xmlns = {
'xhtml' => 'http://www.w3.org/1999/xhtml'
name.to_s.capitalize + " " + ($counters[item][name] += 1).to_s
end
+def img_rep_fallback(item, rep)
+ return rep unless item.reps[rep].raw_path.nil?
+ return :large
+end
+
+def item_to_img(item, rep: :large, alt: nil, caption: nil)
+ unless item
+ return "[image not found]"
+ end
+
+ alt ||= item[:title]
+ caption ||= alt
+ caption = caption.strip
+ caption.gsub!(/\s+/, " ")
+
+ rep = img_rep_fallback(item, rep)
+ attrs = { :src => item_uri(item, rep: rep), :alt => item[:title] }
+ attrs[:width], attrs[:height] = FastImage.size(item.reps[rep].raw_path)
+
+ b = Nokogiri::XML::Builder.new do |xml|
+ xml.a(:href => item_uri(item, rep: :info)) {
+ xml.img(attrs)
+ unless caption.empty?
+ xml << " ⁠"
+ xml.small {
+ xml << caption
+ }
+ end
+ }
+ end
+ b.to_xml(:save_with => Nokogiri::XML::Node::SaveOptions::NO_DECLARATION)
+end
+
+def expand_copyright(copyright)
+ result = { :years => {} }
+
+ /^([-–[:digit:][:space:],]*)(.*)$/.match(copyright) do |m|
+ m[1].split(/\s*,\s*/).each do |range|
+ lo, hi = range.split(/[^[:digit:]]+/)
+ for y in (lo..hi||lo)
+ result[:years][y] = 1
+ end
+ end
+
+ result[:years] = result[:years].keys.sort
+ result[:name] = m[2]
+ return result
+ end
+
+ return nil
+end
+
+def path_to_rep(path)
+ @items.find_all(File::dirname(path) + "/*").each do |item|
+ item.reps.each do |rep|
+ return rep if rep.path == path
+ end
+ end
+ return nil
+end
+
+def find_images(item = @item)
+ return [] if item.binary?
+
+ result = {}
+ doc = Nokogiri::HTML(item.compiled_content(snapshot: :pre))
+ doc.xpath("//img/@src").each do |imgsrc|
+ rep = path_to_rep(imgsrc.value)
+ if rep
+ result[rep.item.identifier] ||= rep.item
+ end
+ end
+ return result.values
+end
+
def human_filesize(size)
units = ["B", "KiB", "MiB", "GiB"]
prec = 0
sprintf("%.*f %s", prec, size + 0.05, unit)
end
+
+def license_shortname(item)
+ item.fetch(:shortname, File::basename(item.identifier.without_ext).upcase)
+end
def do_variant(xml, name, item = @item, rep: :default)
file = item.reps[rep].raw_path
+ unless file.nil?
+ w, h = FastImage.size(file)
+ sz = File.size(file)
- w, h = FastImage.size(file)
- sz = File.size(file)
-
- xml.variant {
- xml.name(name)
- xml.uri(item_uri(item, rep: rep))
- xml.width(w)
- xml.height(h)
- xml.filesize(human_filesize(sz))
- }
+ xml.variant {
+ xml.name(name)
+ xml.uri(item_uri(item, rep: rep))
+ xml.width(w)
+ xml.height(h)
+ xml.filesize(human_filesize(sz))
+ }
+ end
end
def run(filename, params = {})
b = Nokogiri::XML::Builder.new do |xml|
xml.image {
do_variant(xml, "Large", rep: :large)
+ do_variant(xml, "Medium", rep: :medium)
do_variant(xml, "Original")