lib/redcloth/formatters/latex.rb
require 'yaml'
module RedCloth::Formatters::LATEX
include RedCloth::Formatters::Base
def self.entities
@entities ||= YAML.load(File.read(File.dirname(__FILE__)+'/latex_entities.yml'))
end
module Settings
# Maps CSS style names to latex formatting options
def latex_image_styles
@latex_image_class_styles ||= {}
end
end
RedCloth::TextileDoc.send(:include, Settings)
# headers
{ :h1 => 'section',
:h2 => 'subsection',
:h3 => 'subsubsection',
:h4 => 'paragraph',
:h5 => 'subparagraph',
:h6 => 'textbf',
}.each do |m,tag|
define_method(m) do |opts|
case opts[:align]
when 'left' then
"\\begin{flushleft}\\#{tag}{#{opts[:text]}}\\end{flushleft}\n\n"
when 'right' then
"\\begin{flushright}\\#{tag}{#{opts[:text]}}\\end{flushright}\n\n"
when 'center' then
"\\begin{center}\\#{tag}{#{opts[:text]}}\\end{center}\n\n"
else
"\\#{tag}{#{opts[:text]}}\n\n"
end
end
end
# commands
{ :strong => 'textbf',
:em => 'emph',
:i => 'textit',
:b => 'textbf',
:ins => 'underline',
:del => 'sout',
}.each do |m,tag|
define_method(m) do |opts|
"\\#{tag}{#{opts[:text]}}"
end
end
# inline code
def code(opts)
opts[:block] ? opts[:text] : "\\verb@#{opts[:text]}@"
end
# acronyms
def acronym(opts)
"#{opts[:title]} (#{opts[:text]})"
end
# sub/superscripts
{ :sup => '\textsuperscript{#1}',
:sub => '\textsubscript{#1}',
}.each do |m, expr|
define_method(m) do |opts|
expr.sub('#1', opts[:text])
end
end
# environments
{ :pre => 'verbatim',
:cite => 'quote',
}.each do |m, env|
define_method(m) do |opts|
begin_chunk(env) + opts[:text] + end_chunk(env)
end
end
# ignore (or find a good solution later)
[ :span,
:div,
:caps
].each do |m|
define_method(m) do |opts|
opts[:text].to_s
end
end
# lists
{ :ol => 'enumerate',
:ul => 'itemize',
}.each do |m, env|
define_method("#{m}_open") do |opts|
opts[:block] = true
"\\begin{#{env}}\n"
end
define_method("#{m}_close") do |opts|
"\\end{#{env}}\n\n"
end
end
def li_open(opts)
" \\item #{opts[:text]}"
end
def li_close(opts=nil)
"\n"
end
# paragraphs
def p(opts)
case opts[:align]
when 'left' then
"\\begin{flushleft}#{opts[:text]}\\end{flushleft}\n\n"
when 'right' then
"\\begin{flushright}#{opts[:text]}\\end{flushright}\n\n"
when 'center' then
"\\begin{center}#{opts[:text]}\\end{center}\n\n"
else
"#{opts[:text]}\n\n"
end
end
# tables
def td(opts)
column = @table_row.size
if opts[:colspan]
opts[:text] = "\\multicolumn{#{opts[:colspan]}}{ #{"l " * opts[:colspan].to_i}}{#{opts[:text]}}"
end
if opts[:rowspan]
@table_multirow_next[column] = opts[:rowspan].to_i - 1
opts[:text] = "\\multirow{#{opts[:rowspan]}}{*}{#{opts[:text]}}"
end
@table_row.push(opts[:text])
return ""
end
def tr_open(opts)
@table_row = []
return ""
end
def tr_close(opts)
multirow_columns = @table_multirow.find_all {|c,n| n > 0}
multirow_columns.each do |c,n|
@table_row.insert(c,"")
@table_multirow[c] -= 1
end
@table_multirow.merge!(@table_multirow_next)
@table_multirow_next = {}
@table.push(@table_row)
return ""
end
# We need to know the column count before opening tabular context.
def table_open(opts)
@table = []
@table_multirow = {}
@table_multirow_next = {}
return ""
end
# FIXME: need caption and label elements similar to image -> figure
def table_close(opts)
output = "\\begin{table}\n".dup
output << " \\centering\n"
output << " \\begin{tabular}{ #{"l " * @table[0].size }}\n"
@table.each do |row|
output << " #{row.join(" & ")} \\\\\n"
end
output << " \\end{tabular}\n"
output << "\\end{table}\n"
output
end
# code blocks
def bc_open(opts)
opts[:block] = true
begin_chunk("verbatim") + "\n"
end
def bc_close(opts)
end_chunk("verbatim") + "\n"
end
# block quotations
def bq_open(opts)
opts[:block] = true
"\\begin{quotation}\n"
end
def bq_close(opts)
"\\end{quotation}\n\n"
end
# links
def link(opts)
"\\href{#{opts[:href]}}{#{opts[:name]}}"
end
# FIXME: use includegraphics with security verification
#
# Remember to use '\RequirePackage{graphicx}' in your LaTeX header
#
# FIXME: Look at dealing with width / height gracefully as this should be
# specified in a unit like cm rather than px.
def image(opts)
# Don't know how to use remote links, plus can we trust them?
return "" if opts[:src] =~ /^\w+\:\/\//
# Resolve CSS styles if any have been set
styling = opts[:class].to_s.split(/\s+/).collect { |style| latex_image_styles[style] }.compact.join ','
# Build latex code
[ "\\begin{figure}",
" \\centering",
" \\includegraphics[#{styling}]{#{opts[:src]}}",
(" \\caption{#{escape opts[:title]}}" if opts[:title]),
(" \\label{#{escape opts[:alt]}}" if opts[:alt]),
"\\end{figure}",
].compact.join "\n"
end
# footnotes
def footno(opts)
# TODO: insert a placeholder until we know the footnote content.
# For this to work, we need some kind of post-processing...
"\\footnotemark[#{opts[:text]}]"
end
def fn(opts)
"\\footnotetext[#{opts[:id]}]{#{opts[:text]}}"
end
# inline verbatim
def snip(opts)
"\\verb`#{opts[:text]}`"
end
def quote1(opts)
"`#{opts[:text]}'"
end
def quote2(opts)
"``#{opts[:text]}''"
end
def ellipsis(opts)
"#{opts[:text]}\\ldots{}"
end
def emdash(opts)
"---"
end
def endash(opts)
" -- "
end
def arrow(opts)
"\\rightarrow{}"
end
def trademark(opts)
"\\texttrademark{}"
end
def registered(opts)
"\\textregistered{}"
end
def copyright(opts)
"\\copyright{}"
end
# TODO: what do we do with (unknown) unicode entities ?
#
def entity(opts)
text = opts[:text][0..0] == '#' ? opts[:text][1..-1] : opts[:text]
RedCloth::Formatters::LATEX.entities[text]
end
def dim(opts)
opts[:text].gsub!('x', '\times')
opts[:text].gsub!('"', "''")
period = opts[:text].slice!(/\.$/)
"$#{opts[:text]}$#{period}"
end
# TODO: what do we do with HTML?
def inline_html(opts)
opts[:text] || ""
end
private
def escape(text)
latex_esc(text)
end
def escape_pre(text)
text
end
# Use this for block level commands that use \begin
def begin_chunk(type)
chunk_counter[type] += 1
return "\\begin{#{type}}" if 1 == chunk_counter[type]
''
end
# Use this for block level commands that use \end
def end_chunk(type)
chunk_counter[type] -= 1
raise RuntimeError, "Bad latex #{type} nesting detected" if chunk_counter[type] < 0 # This should never need to happen
return "\\end{#{type}}" if 0 == chunk_counter[type]
''
end
def chunk_counter
@chunk_counter ||= Hash.new 0
end
end