Improved Image Captions in Octopress


Extending Imgcap with relative sizing, alt text, and markdown formatting

Posted on Friday, November 28, 2014 by Charles Beynon
× Charles Beynon
[Photo: Dark-haired caucasian male, mid-thirties, wearing dark-rimmed eyeglasses]

Full Stack Developer mostly working in Rails. Recovering sysadmin. I like to dabble and tend to ramble. Don't say I didn't warn you ;D

About me

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" %}
Leonhard Euler
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.io).
Captions can be _much_ longer and more descriptive, and be split onto
multiple lines in the source file.
{% endimgcaption %}
[Painting - A classical portrait of Leonard Euler]

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

Image Caption Tag & Blocklink
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!(/"/, '&#34;') 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!(/"/, '&#34;')
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
  1. 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. 


Comments