Creating 'S5' RedCloth Presentations

Overview

As you may gather, I’ve been abusing RedCloth a lot recently, which is also reflected by the fact I’ve submitted a number of bug reports before the official release and a couple of small patches.

Like the old 3.x branch, you can create custom block commands. However due to the C/Ragel based parser the implementation details are incompatible. I’m going to illustrate how we can use Eric Meyer’s S5 tool for making XHTML based presentations and combine that with the new RedCloth 4.x formatter system in order to extend on the existing Textile mark-up system.

The Implementation

New Textile Commands

RedCloth 4.x can support custom block items thanks to a patch added by Tim Pease. Basically what we are going to do is make a new module that will provide two extra commands, slide and annotation to the existing Textile system when rendering to S5 format. This is done by having methods called slide and annotation in our custom formatter module. All block commands accept a hash table instance which contains any optional attributes and the block text (with in-line Textile commands pre processed). The S5 formatter is basically an extension of HTML formatting and you will see how we inherit the same formatting support though the include directive.

S5 Redcloth Formatter Code Listing

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
require 'rubygems'
require 'redcloth'
require 'erb'

module RedCloth
  # Patch up with a handy to_s5 method.
  class TextileDoc
    
    attr_accessor :settings
    
    def to_s5(*config)
      self.settings = config.last.is_a?(Hash) ? config.pop : {}
      apply_rules config
      to RedCloth::Formatters::S5
    end
  end
  
  # = S5 Based XHTML Formatter
  #
  # Based on the S5 system at http://meyerweb.com/eric/tools/s5/ this derives 
  # from the HTML formatter to provide a basic slide 
  module Formatters::S5
    
    include RedCloth::Formatters::HTML, ERB::Util
    attr_reader :settings
    
    def render(scope = nil, template_file = nil)
      template_file ||= settings[:template] || 'default.html.erb'
      template_data   = File.read template_file
      code = ERB.new template_data, nil, '-'
      render_template code, scope
    end
    
    def render_template(code, scope = nil)
      scope ||= binding
      eval code.src, scope
    end
    
    def slide(opts)
      slide_end + "\n\n" + slide_start(opts) + "\n"
    end
    
    def after_transform(text)
      content = text
      content << slide_end if @slides_used
      text[0..-1] = render binding # Replacement hack!
    end
    
    def slide_start(opts = {})
      @slides_used  ||= true
      (opts[:class] ||= '') << ' slide'
      %[<div#{pba opts}><h1>#{h opts[:text]}</h1><div class="slidecontent">]
    end
    
    def annotation(opts)
      '</div><div class="handout">' + opts[:text].to_s
    end
    
    def slide_end
      '</div></div>'
    end
  end
end

In order to allow for customisation of the main S5 mark-up, I will use ERB (as it’s comes with Ruby) to do this. When rendering to S5 format using RedCloth#to_s5 instead of RedCloth#to_html we can provide additional details such as the author, template file, a custom footer, etc.

As you will see most of the code is in a single module which as a few ERB helper methods, we include ERB::Util too so our template can use methods such as h and u.

You may see that I’m doing something particularly odd with the after_transform call back method. Basically the processed text is passed to it as a parameter and needs to be treated with reference semantics, despite the fact strings normally use value semantics by default in most scripting languages such as Ruby. If your from a C/C++ background, this will be a fairly normal idiom. Basically, you need to modify actual the contents of text parameter, rather than return a modified version. This is most probably because it was easier from an implementation point of view, and is marginally faster I’m guessing.

Usage.

Typically, you should be able to do something like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
slide_show = <<-TEXT
slide. Hello 1

p. This is the first slide! 

p. Notice how the slide block contents are made into the slide header.

slide. Hello 2

p. This is the second slide content

* Here is a very ...
* Basic example of a ...
* Bullet List.

annotation. 
This is only shown on printed versions and not on display versions. 
Annotations can help act as guides or printed hand out content.

slide(third #item). Hello 3

p. This is the third slide content
TEXT

show = RedCloth.new(slide_show).to_s5 \
  :title  => 'S5 on Red Cloth Example',
  :author => 'Jason Earl',
  :footer => 'Custom footer content can go here'

Support Files

By default the RedCloth#to_s5 method will look for a default.html.erb file in the current folder. You might want to change the location of this. You will probably want to save the result of the RedCloth#to_s5 method somewhere. This should have a sub folder containing the ui/default files from the original S5 package, as this contains CSS and JavaScript files that control the presentation system.

Sample default.html.erb

You can use this as a starting point / default template for S5 presentations. This needs to be in the same folder as the S5 formatter module.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">

<head>
<title><%= h settings[:title] %></title>
<!-- metadata -->
<meta name="generator" content="S5" />
<meta name="version" content="S5 1.1" />
<meta name="presdate" content="<%= Time.now.strftime('%Y%m%d') %>" />
<% if settings[:author] %>
<meta name="author" content="<%= h settings[:author] %>" />
<% end %>
<% if settings[:company] %>
<meta name="company" content="<%= h settings[:company] %>" />
<% end %>
<!-- configuration parameters -->
<meta name="defaultView" content="slideshow" />
<meta name="controlVis" content="hidden" />
<!-- style sheet links -->
<link rel="stylesheet" href="ui/default/slides.css" type="text/css" media="projection" id="slideProj" />
<link rel="stylesheet" href="ui/default/outline.css" type="text/css" media="screen" id="outlineStyle" />
<link rel="stylesheet" href="ui/default/print.css" type="text/css" media="print" id="slidePrint" />
<link rel="stylesheet" href="ui/default/opera.css" type="text/css" media="projection" id="operaFix" />
<!-- S5 JS -->
<script src="ui/default/slides.js" type="text/javascript"></script>
</head>
<body>

<div class="layout">
<div id="controls"><!-- DO NOT EDIT --></div>
<div id="currentSlide"><!-- DO NOT EDIT --></div>
<div id="header"></div>
<div id="footer">
<%= RedCloth.new(settings[:footer].to_s).to_html %>
</div>
</div>

<div class="presentation">
<%= content %>
</div>

</body>
</html>

Closing Words

Hopefully someone has found this a handy concept, and maybe has given others some idea how they can make their own extensions to RedCloth 4.x through the new formatter system. One of the nice things is that if you use an other formatter, the annotation and slide commands will simply be ignored and be rendered though the paragraph fallback, thus the case of generic XHTML formatting, will appear as a p tag. Obviously, one can use textile class names and custom CSS to control how these get displayed in other formats.

Between mocking this up and writing this post, I realised that there is a slideshow gem on RubyForge that works in a similar way. Slideshow offers some SVG graphics support which would be handy given the dynamic nature of screen resolutions.

Listening to Ambient / IDM:
Hedphelym - Aphex Twin

Free Designs

Below are a handful of open source / free web designs