class Kramdown::Converter::Latex

Converts an element tree to LaTeX.

This converter uses ideas from other Markdown-to-LaTeX converters like Pandoc and Maruku.

You can customize this converter by sub-classing it and overriding the convert_NAME methods. Each such method takes the following parameters:

el

The element of type NAME to be converted.

opts

A hash containing processing options that are passed down from parent elements. The key :parent is always set and contains the parent element as value.

The return value of such a method has to be a string containing the element el formatted correctly as LaTeX markup.

Public Class Methods

new(root, options) click to toggle source

Initialize the LaTeX converter with the root element and the conversion options.

Calls superclass method
# File lib/kramdown/converter/latex.rb, line 33
def initialize(root, options)
  super
  @data[:packages] = Set.new
end

Public Instance Methods

attribute_list(el) click to toggle source

Return a LaTeX comment containing all attributes as 'key=“value”' pairs.

# File lib/kramdown/converter/latex.rb, line 598
def attribute_list(el)
  attrs = el.attr.map {|k, v| v.nil? ? '' : " #{k}=\"#{v}\"" }.compact.sort.join('')
  attrs = "   % #{attrs}" unless attrs.empty?
  attrs
end
convert(el, opts = {}) click to toggle source

Dispatch the conversion of the element el to a convert_TYPE method using the type of the element.

# File lib/kramdown/converter/latex.rb, line 40
def convert(el, opts = {})
  send("convert_#{el.type}", el, opts)
end
convert_a(el, opts) click to toggle source
# File lib/kramdown/converter/latex.rb, line 215
def convert_a(el, opts)
  url = el.attr['href']
  if url.start_with?('#')
    "\\hyperlink{#{url[1..-1].gsub('%', '\\%')}}{#{inner(el, opts)}}"
  else
    "\\href{#{url.gsub('%', '\\%')}}{#{inner(el, opts)}}"
  end
end
convert_abbreviation(el, _opts) click to toggle source
# File lib/kramdown/converter/latex.rb, line 568
def convert_abbreviation(el, _opts)
  @data[:packages] += %w[acronym]
  "\\ac{#{normalize_abbreviation_key(el.value)}}"
end
convert_blank(_el, opts) click to toggle source
# File lib/kramdown/converter/latex.rb, line 60
def convert_blank(_el, opts)
  opts[:result] =~ /\n\n\Z|\A\Z/ ? "" : "\n"
end
convert_blockquote(el, opts) click to toggle source
# File lib/kramdown/converter/latex.rb, line 109
def convert_blockquote(el, opts)
  latex_environment(el.children.size > 1 ? 'quotation' : 'quote', el, inner(el, opts))
end
convert_br(_el, opts) click to toggle source
# File lib/kramdown/converter/latex.rb, line 208
def convert_br(_el, opts)
  res = +"\\newline"
  res << "\n" if (c = opts[:parent].children[opts[:index] + 1]) &&
    (c.type != :text || c.value !~ /^\s*\n/)
  res
end
convert_codeblock(el, _opts) click to toggle source
# File lib/kramdown/converter/latex.rb, line 86
def convert_codeblock(el, _opts)
  show_whitespace = el.attr['class'].to_s =~ /\bshow-whitespaces\b/
  lang = extract_code_language(el.attr)

  if @options[:syntax_highlighter] == :minted &&
      (highlighted_code = highlight_code(el.value, lang, :block))
    @data[:packages] << 'minted'
    "#{latex_link_target(el)}#{highlighted_code}\n"
  elsif show_whitespace || lang
    options = []
    options << (show_whitespace ? "showspaces=true,showtabs=true" : "showspaces=false,showtabs=false")
    options << "language=#{lang}" if lang
    options << "basicstyle=\\ttfamily\\footnotesize,columns=fixed,frame=tlbr"
    id = el.attr['id']
    options << "label=#{id}" if id
    attrs = attribute_list(el)
    "#{latex_link_target(el)}\\begin{lstlisting}[#{options.join(',')}]\n" \
      "#{el.value}\n\\end{lstlisting}#{attrs}\n"
  else
    "#{latex_link_target(el)}\\begin{verbatim}#{el.value}\\end{verbatim}\n"
  end
end
convert_codespan(el, _opts) click to toggle source
# File lib/kramdown/converter/latex.rb, line 238
def convert_codespan(el, _opts)
  lang = extract_code_language(el.attr)
  if @options[:syntax_highlighter] == :minted &&
      (highlighted_code = highlight_code(el.value, lang, :span))
    @data[:packages] << 'minted'
    "#{latex_link_target(el)}#{highlighted_code}"
  else
    "\\texttt{#{latex_link_target(el)}#{escape(el.value)}}"
  end
end
convert_comment(el, _opts) click to toggle source
# File lib/kramdown/converter/latex.rb, line 204
def convert_comment(el, _opts)
  el.value.split(/\n/).map {|l| "% #{l}" }.join("\n") << "\n"
end
convert_dd(el, opts) click to toggle source
# File lib/kramdown/converter/latex.rb, line 150
def convert_dd(el, opts)
  "#{latex_link_target(el)}#{inner(el, opts)}\n\n"
end
convert_dl(el, opts) click to toggle source
# File lib/kramdown/converter/latex.rb, line 138
def convert_dl(el, opts)
  latex_environment('description', el, inner(el, opts))
end
convert_dt(el, opts) click to toggle source
# File lib/kramdown/converter/latex.rb, line 146
def convert_dt(el, opts)
  "\\item[#{inner(el, opts)}] "
end
convert_em(el, opts) click to toggle source
# File lib/kramdown/converter/latex.rb, line 262
def convert_em(el, opts)
  "\\emph{#{latex_link_target(el)}#{inner(el, opts)}}"
end
convert_entity(el, _opts) click to toggle source
# File lib/kramdown/converter/latex.rb, line 532
def convert_entity(el, _opts)
  entity_to_latex(el.value)
end
convert_footnote(el, opts) click to toggle source
# File lib/kramdown/converter/latex.rb, line 249
def convert_footnote(el, opts)
  @data[:packages] << 'fancyvrb'
  "\\footnote{#{inner(el.value, opts).rstrip}}"
end
convert_header(el, opts) click to toggle source
# File lib/kramdown/converter/latex.rb, line 113
def convert_header(el, opts)
  type = @options[:latex_headers][output_header_level(el.options[:level]) - 1]
  if ((id = el.attr['id']) ||
      (@options[:auto_ids] && (id = generate_id(el.options[:raw_text])))) && in_toc?(el)
    "\\#{type}{#{inner(el, opts)}}\\hypertarget{#{id}}{}\\label{#{id}}\n\n"
  else
    "\\#{type}*{#{inner(el, opts)}}\n\n"
  end
end
convert_hr(el, _opts) click to toggle source
# File lib/kramdown/converter/latex.rb, line 123
def convert_hr(el, _opts)
  attrs = attribute_list(el)
  "#{latex_link_target(el)}\\begin{center}#{attrs}\n\\rule{3in}{0.4pt}\n\\end{center}#{attrs}\n"
end
convert_html_element(el, opts) click to toggle source
# File lib/kramdown/converter/latex.rb, line 154
def convert_html_element(el, opts)
  if el.value == 'i' || el.value == 'em'
    "\\emph{#{inner(el, opts)}}"
  elsif el.value == 'b' || el.value == 'strong'
    "\\textbf{#{inner(el, opts)}}"
  else
    warning("Can't convert HTML element")
    ''
  end
end
convert_img(el, _opts) click to toggle source
# File lib/kramdown/converter/latex.rb, line 224
def convert_img(el, _opts)
  line = el.options[:location]
  if el.attr['src'] =~ /^(https?|ftps?):\/\//
    warning("Cannot include non-local image#{line ? " (line #{line})" : ''}")
    ''
  elsif !el.attr['src'].empty?
    @data[:packages] << 'graphicx'
    "#{latex_link_target(el)}\\includegraphics{#{el.attr['src']}}"
  else
    warning("Cannot include image with empty path#{line ? " (line #{line})" : ''}")
    ''
  end
end
convert_li(el, opts) click to toggle source
# File lib/kramdown/converter/latex.rb, line 142
def convert_li(el, opts)
  "\\item{} #{latex_link_target(el, true)}#{inner(el, opts).sub(/\n+\Z/, '')}\n"
end
convert_math(el, _opts) click to toggle source
# File lib/kramdown/converter/latex.rb, line 555
def convert_math(el, _opts)
  @data[:packages] += %w[amssymb amsmath amsthm amsfonts]
  if el.options[:category] == :block
    if el.value =~ /\A\s*\\begin\{/
      el.value
    else
      latex_environment('displaymath', el, el.value)
    end
  else
    "$#{el.value}$"
  end
end
convert_ol(el, opts)
Alias for: convert_ul
convert_p(el, opts) click to toggle source
# File lib/kramdown/converter/latex.rb, line 68
def convert_p(el, opts)
  if el.children.size == 1 && el.children.first.type == :img &&
      !(img = convert_img(el.children.first, opts)).empty?
    convert_standalone_image(el, opts, img)
  else
    "#{latex_link_target(el)}#{inner(el, opts)}\n\n"
  end
end
convert_raw(el, _opts) click to toggle source
# File lib/kramdown/converter/latex.rb, line 254
def convert_raw(el, _opts)
  if !el.options[:type] || el.options[:type].empty? || el.options[:type].include?('latex')
    el.value + (el.options[:category] == :block ? "\n" : '')
  else
    ''
  end
end
convert_root(el, opts) click to toggle source
# File lib/kramdown/converter/latex.rb, line 56
def convert_root(el, opts)
  inner(el, opts)
end
convert_smart_quote(el, opts) click to toggle source
# File lib/kramdown/converter/latex.rb, line 549
def convert_smart_quote(el, opts)
  res = entity_to_latex(smart_quote_entity(el)).chomp('{}')
  res << "{}" if ((nel = opts[:parent].children[opts[:index] + 1]) && nel.type == :smart_quote) || res =~ /\w$/
  res
end
convert_standalone_image(el, _opts, img) click to toggle source

Helper method used by convert_p to convert a paragraph that only contains a single :img element.

# File lib/kramdown/converter/latex.rb, line 79
def convert_standalone_image(el, _opts, img)
  attrs = attribute_list(el)
  "\\begin{figure}#{attrs}\n\\begin{center}\n#{img}\n\\end{center}\n" \
    "\\caption{#{escape(el.children.first.attr['alt'])}}\n" \
    "#{latex_link_target(el, true)}\n\\end{figure}#{attrs}\n"
end
convert_strong(el, opts) click to toggle source
# File lib/kramdown/converter/latex.rb, line 266
def convert_strong(el, opts)
  "\\textbf{#{latex_link_target(el)}#{inner(el, opts)}}"
end
convert_table(el, opts) click to toggle source
# File lib/kramdown/converter/latex.rb, line 176
def convert_table(el, opts)
  @data[:packages] << 'longtable'
  align = el.options[:alignment].map {|a| TABLE_ALIGNMENT_CHAR[a] }.join('|')
  attrs = attribute_list(el)
  "#{latex_link_target(el)}\\begin{longtable}{|#{align}|}#{attrs}\n" \
    "\\hline\n#{inner(el, opts)}\\hline\n\\end{longtable}#{attrs}\n\n"
end
convert_tbody(el, opts) click to toggle source
# File lib/kramdown/converter/latex.rb, line 188
def convert_tbody(el, opts)
  inner(el, opts)
end
convert_td(el, opts) click to toggle source
# File lib/kramdown/converter/latex.rb, line 200
def convert_td(el, opts)
  inner(el, opts)
end
convert_text(el, _opts) click to toggle source
# File lib/kramdown/converter/latex.rb, line 64
def convert_text(el, _opts)
  escape(el.value)
end
convert_tfoot(el, opts) click to toggle source
# File lib/kramdown/converter/latex.rb, line 192
def convert_tfoot(el, opts)
  "\\hline \\hline \n#{inner(el, opts)}"
end
convert_thead(el, opts) click to toggle source
# File lib/kramdown/converter/latex.rb, line 184
def convert_thead(el, opts)
  "#{inner(el, opts)}\\hline\n"
end
convert_tr(el, opts) click to toggle source
# File lib/kramdown/converter/latex.rb, line 196
def convert_tr(el, opts)
  el.children.map {|c| send("convert_#{c.type}", c, opts) }.join(' & ') << "\\\\\n"
end
convert_typographic_sym(el, _opts) click to toggle source
# File lib/kramdown/converter/latex.rb, line 541
def convert_typographic_sym(el, _opts)
  if (result = @options[:typographic_symbols][el.value])
    escape(result)
  else
    TYPOGRAPHIC_SYMS[el.value]
  end
end
convert_ul(el, opts) click to toggle source
# File lib/kramdown/converter/latex.rb, line 128
def convert_ul(el, opts)
  if !@data[:has_toc] && el.options.dig(:ial, :refs)&.include?('toc')
    @data[:has_toc] = true
    '\tableofcontents'
  else
    latex_environment(el.type == :ul ? 'itemize' : 'enumerate', el, inner(el, opts))
  end
end
Also aliased as: convert_ol
convert_xml_comment(el, _opts) click to toggle source
# File lib/kramdown/converter/latex.rb, line 165
def convert_xml_comment(el, _opts)
  el.value.split(/\n/).map {|l| "% #{l}" }.join("\n") + "\n"
end
convert_xml_pi(_el, _opts) click to toggle source
# File lib/kramdown/converter/latex.rb, line 169
def convert_xml_pi(_el, _opts)
  warning("Can't convert XML PI")
  ''
end
entity_to_latex(entity) click to toggle source
# File lib/kramdown/converter/latex.rb, line 521
def entity_to_latex(entity)
  text, package = ENTITY_CONV_TABLE[entity.code_point]
  if text
    @data[:packages] << package if package
    text
  else
    warning("Couldn't find entity with code #{entity.code_point} in substitution table!")
    ''
  end
end
escape(str) click to toggle source

Escape the special LaTeX characters in the string str.

# File lib/kramdown/converter/latex.rb, line 617
def escape(str)
  str.gsub(ESCAPE_RE) {|m| ESCAPE_MAP[m] }
end
inner(el, opts) click to toggle source

Return the converted content of the children of el as a string.

# File lib/kramdown/converter/latex.rb, line 45
def inner(el, opts)
  result = +''
  options = opts.dup.merge(parent: el)
  el.children.each_with_index do |inner_el, index|
    options[:index] = index
    options[:result] = result
    result << send("convert_#{inner_el.type}", inner_el, options)
  end
  result
end
latex_environment(type, el, text) click to toggle source

Wrap the text inside a LaTeX environment of type type. The element el is passed on to the method attribute_list – the resulting string is appended to both the \begin and the \end lines of the LaTeX environment for easier post-processing of LaTeX environments.

# File lib/kramdown/converter/latex.rb, line 581
def latex_environment(type, el, text)
  attrs = attribute_list(el)
  "\\begin{#{type}}#{latex_link_target(el)}#{attrs}\n#{text.rstrip}\n\\end{#{type}}#{attrs}\n"
end
normalize_abbreviation_key(key) click to toggle source

Normalize the abbreviation key so that it only contains allowed ASCII character

# File lib/kramdown/converter/latex.rb, line 574
def normalize_abbreviation_key(key)
  key.gsub(/\W/) {|m| m.unpack('H*').first }
end