Improved Image Captions in Octopress
Extending Imgcap with relative sizing, alt text, and markdown formatting
Main Content
Thanks to Robert Anderson’s Image Caption tag, Octopress blogs have had the option to add captions to images for some time. There were some limitations I wanted to improve upon so I began hacking away, and created a block version that allows Markdown formatting in the caption text. In addition, I enhanced the treatment of alt tags and relative widths.
First, a quick demo of the original imgcap
tag, the example from Robert
Anderson’s blog. Using syntax pretty much identical to the standard Octopress
img
tag:
{% imgcap "https://some.url.com/pic.jpg" 200 200 "Leonhard Euler" %}

This leads to captioned images like the one to the left. It supports left, right, and center image classes, and absolute sizing, making it fairly versatile.
tl;dr: The final source code is at the end of this post
The Alternate Solution
One minor issue I had was the lack of separately specifiable alt
text.
As a partially visually-impaired user, I’m sensitive to the needs readers who
rely on screen readers, and title text serves a different purpose from
alt text1.
So my first iteration was to study the built-in Octopress img
tag and
incorporate its implementation of the alt tag into the imgcap
tag. An example
use of the extended tag:
{% imgcap "https://some.url.com/pic.jpg" 200 200 "Leonhard Euler" "[Painting - A classical portrait of Leonard Euler]" %}
New Captions on the Block
OK, great, now imgcap
has the alt text function of img
, so I was happy.
But it still reuses the title (mouse-over) text for the caption. Since Liquid
tags can’t be split on multiple lines, longer captions lead to extraordinarily
long lines in the markdown sauce, and the caption text can’t be formatted.
So my next iteration involved studying Octopress’ built-in blocks (mostly the
block-quote and pull-quote blocks), and implementing imgcap
as imgcaption
.
Using the new block tag, a captioned image can be inserted as follows:
{% imgcaption right "https://some.url.com/pic.jpg" 200 200 "Leonard Euler" "[Painting - A classical portrait of Leonard Euler]" %}
**Leonard Euler** is the namesake of [this blog](https://eulerpi.dev).
Captions can be _much_ longer and more descriptive, and be split onto
multiple lines in the source file.
{% endimgcaption %}
![Leonard Euler [Painting - A classical portrait of Leonard Euler]](https://upload.wikimedia.org/wikipedia/commons/d/d7/Leonhard_Euler.jpg)
Leonard Euler is the namesake of this blog. Captions can be much longer and more descriptive, and be split onto multiple lines in the source file.
Now links, formatted text, and even raw HTML can be added to the image captions, giving about as much versatility as I needed.
Size matters not. It’s All Relative
Unfortunately, in the original version large captions weren’t properly wrapped to fit the image size; the shadow box expanded to fit the caption. Also, one related limitation. The dimensions could only be expressed in absolute terms.
The solution to both requires slightly different HTML output, depending on absolute or relative sizes are wanted for the image. For relative sizes, the width needs to be defined for the outer shadow box span, with the inner elements having a width of 100% to fill the shadow box, resulting in HTML something like this:
<span class="content-wrapper" style='width:xx%;'>
<img src="/images/pic.jpg" width="100%" height="100%">
<span class="content-caption">Caption Text</span>
</span>
Meanwhile, for absolute sizes, the inner elements need to be sized, and the caption span needs to be adjusted for the margin sizes. This results in HTML output like the following:
<span class="content-wrapper">
<img src="/images/pic.jpg" width="200px" height="300px">
<span class="content-caption" style="width:190px;">Caption Text</span>
</span>
The Final Code
module Jekyll
# Easily output images with captions
class CaptionImageTag < Liquid::Tag
@img = nil
@title = nil
@class = ''
@width = ''
@height = ''
def initialize(tag_name, markup, tokens)
if markup =~ %r{(?<classname>\S.*\s+)?(?<protocol>https?://|/)(?<src>\S+)(<?size>\s+\d+%?\s+\d+%?)?(<?title>\s+.+)?}i
@class = classname || ''
@img = protocol + src
@title = title.strip if title
parse_sizes(size)
parse_title(@title)
end
super
end
def render(context)
super
if @img && @width[-1] == "%" # Relative width, so width goes on outer span
"<span class='#{('caption-wrapper ' + @class).rstrip}' style='width:#{@width};'>" +
"<img class='caption' src='#{@img}' width='100%' height='100%' title='#{@title}' alt='#{@alt}'>" +
"<span class='caption-text'>#{@title}</span>" +
"</span>"
elsif @img # Absolute width, so width goes on the img tag and text span gets sytle-width:@width-15;
"<span class='#{('caption-wrapper ' + @class).rstrip}'>" \
"<img class='caption' src='#{@img}' width='#{@width}px' height='#{@height}px' title='#{@title}' alt='#{@alt}'>" \
"<span class='caption-text' style='width:#{@width.to_i - 10}px;'>#{@title}</span>" \
"</span>"
else
'Error processing input, expected syntax: {% imgcap [class name(s)] /url/to/image [width height] [title [alt]] %}'
end
end
end
class CaptionImageBlock < Liquid::Block
@img = nil
@title = nil
@class = ''
@width = ''
@height = ''
def initialize(tag_name, markup, tokens)
if markup =~ /(\S.*\s+)?(https?:\/\/|\/)(\S+)(\s+\d+%?\s+\d+%?)?(\s+.+)?/i
@class = $1 || ''
@img = $2 + $3
if $5
@title = $5.strip
end
if $4 =~ /\s*(\d+%?)\s+(\d+%?)/
@width = $1
@height = $2
elsif @class.rstrip == "right" or @class.rstrip == "left"
@width = "33%"
else
@width = "100%"
end
if /(?:"|')(?<title>[^"']+)?(?:"|')\s+(?:"|')(?<alt>[^"']+)?(?:"|')/ =~ @title
@title = title
@alt = alt
else
@alt = @title.gsub!(/"/, '"') if @title
end
end
super
end
def render(context)
@caption = super
if @img && @width[-1] == "%" # Relative width, so width goes on outer span
"<span class='#{('caption-wrapper ' + @class).rstrip}' style='width:#{@width};'>" +
"<img class='caption' src='#{@img}' width='100%' height='100%' title='#{@title}' alt='#{@alt}'>" +
"<span class='caption-text'>#{@caption}</span>" +
"</span>"
elsif @img # Absolute width, so width goes on the img tag and text span gets sytle-width:@width-15;
"<span class='#{('caption-wrapper ' + @class).rstrip}'>" +
"<img class='caption' src='#{@img}' width='#{@width}px' height='#{@height}px' title='#{@title}' alt='#{@alt}'>" +
"<span class='caption-text' style='width:#{@width.to_i - 10}px;'>#{@caption}</span>" +
"</span>"
else
"Error processing input, expected syntax: {% imgcaption [class name(s)] /url/to/image [width height] [title [alt]] %} Caption Text {% endimgcaption %}"
end
end
end
end
Liquid::Template.register_tag('imgcaption', Jekyll::CaptionImageBlock)
Liquid::Template.register_tag('imgcap', Jekyll::CaptionImageTag)
private
def parse_title(raw_title)
if raw_title.nil?
@title = ''
@alt = ''
end
if /(?:"|')(?<title>[^"']+)?(?:"|')\s+(?:"|')(?<alt>[^"']+)?(?:"|')/ =~ raw_title
@title = title
@alt = alt
else
@alt = raw_title.gsub!(/"/, '"')
end
end
def parse_size(raw_size)
if raw_size =~ /\s*(?<w>\d+%?)\s+(<?h>\d+%?)/
@width = w
@height = h
elsif @class.rstrip == 'right' || @class.rstrip == 'left'
@height = '100%'
@width = '33%'
else
@height = '100%'
@width = '100%'
end
end
-
Title text adds flavor or contextual meta-information to an image which not contained within the image itself, while alt text provides the important contextual information within the image which is not contained in the surrounding text. ↩