#
# File: latex-converter.rb
# Author: J. Carlson (jxxcarlson@gmail.com)
# First commit: 9/26/2014
# Date: 2/20/2015
#
# OVERVIEW
#
# Asciidoctor-latex adds certain latex-like constructs to the asciidoc markup
# language and provides two backends:
#
# - a converter with HTML as output
#
# -- a convert with LaTeX as output
#
# The goal of this project is to be able to combine the
# strengths of asciidoc and late:
#
# - to use asciidoc for most of the non-mathematical text
#
# - to write inline mathematical text as one does in LaTeX:
#
# $ a^2 + b^2 = c^1 $,
#
# for example.
#
# - the same for displayed mathematical text
#
# \[
# \int_0^1 x^n dx = \frac{1}{n+1}
# \]
#
# - to provide constructs which map to LaTeX environments.
# Thus one can say
#
# [env.theorem]
# --
# There are infinitely many primes
# --
#
# In the HTML converter the output will appear just
# as if it had been rendered by xelatex. If the
# LaTeX backend is used, the output is as one would
# expect
#
# See http://www.noteshare.io/section/asciidoctor-latex-manual-intro
# for a description (with examples) of asciidoctor-latex.
#
# INSTALLATION
#
# Run 'rake install' to install the asciidoctor-latex gem locally.
#
# USAGE (1: html)
#
# Run
#
# 'asciidoctor-latex -b html foo.adoc'
#
# to render an asciidoctor-latex document to html
#
# Some documents -- those with 'click blocks',
# use a variant of the regular command:
#
# 'asciidoctor-latex -b html -a click_extras=include foo.adoc'
#
# In both cases the output is 'foo.html'
# see http://www.noteshare.io/section/homework-problems)
# for information on this topic.
#
# USAGE (2: latex)
#
# Run
#
# 'asciidoctor-latex foo.adoc'
#
# to convert foo.adoc to foo.tex. The resulting
# file should be typeset with xelatex
# because of the likely appearance of unicode
# characters.
#
# To convert foo.tex to the latex file foo.latex,
# You can put files 'macros.tex' and 'preamble.tex' to
# replace the default preamble and set of macros.
# Define your own 'newEnvironments.tex' to use your definitions
# as opposed to the default definitions of environments
# in tex mapping to [tex.ENVIRONOMENT] in asciidoctor-latex.
#
# TECHNICAL NOTES (This section needs a thorough rewrite)
#
# This is a first step towards writing a LaTeX backend
# for Asciidoctor. It is based on the
# Dan Allen's demo-converter.rb.
#
# The main work will be in identifying asciidoc elements
# that need to be transformed and adding a method for
# each such element. As noted below, the "warn" clause
# in the "convert" method is a useful tool for this task.
#
# Comments
#
# 1. The "warn" clause in the converter code is quite useful.
# For example, you may discover in running the converter on
# "test/sample-1.adoc" that you have not implemented code for
# the "olist" node. Thus you can work through ever more complex
# examples to discover what you need to do to increase the coverage
# of the converter. Hackish and ad hoc, but a process nonetheless.
#
# 2. The converter simply passes on what it does not understand, e.g.,
# LaTeX, This is good. However, we will have to map constructs
# like "+\( a^2 = b^2 \)+" to $ a^2 + b^2 $, etc.
# This can be done at the preprocessor level.
#
# 3. In view of the preceding, we may need to chain a frontend
# (preprocessor) to the backend. In any case, the main work
# is in transforming Asciidoc elements to TeX elements.
# Other than the asciidoc -> tex mapping, the tex-converter
# does not need to understand tex.
#
# 4. Included in this repo are the files "test/sample1.adoc", "test/sample2.adoc",
# and "test/elliptic.adoc" which can be used to test the code
#
# 5. Beginning with version 0.0.2 we use a new dispatch mechanism
# which should permit one to better manage growth of the code
# as the coverage of the converter increases. Briefly, the
# main convert method, whose duty is to process nodes, looks
# at node.node_name, then makes the method call node.tex_process
# if the node_name is registered in NODE_TYPES. The method
# tex_process is defined by extending the various classes to
# which the node might belong, e.g., Asciidoctor::Block,
# Asciidoctor::Inline, etc. See the file "node_processor.rb",
# where these extensions are housed for the time being.
#
# If node.node_name is not found in NODE_TYPES, then
# a warning message is issued. We can use it as a clue
# to find what to do to handle this node. All the code
# in "node_processors.rb" to date was written using this
# hackish process.
#
require 'asciidoctor'
require 'asciidoctor/extensions' unless RUBY_ENGINE == 'opal'
require 'asciidoctor/converter/html5'
require 'asciidoctor/latex/css_doc_info'
require 'asciidoctor/latex/inline_macros'
require 'asciidoctor/latex/core_ext/colored_string'
require 'asciidoctor/latex/click_block'
require 'asciidoctor/latex/inject_html'
require 'asciidoctor/latex/ent_to_uni'
require 'asciidoctor/latex/environment_block'
require 'asciidoctor/latex/node_processors'
require 'asciidoctor/latex/prepend_processor'
require 'asciidoctor/latex/macro_insert'
require 'asciidoctor/latex/tex_block'
require 'asciidoctor/latex/tex_preprocessor'
require 'asciidoctor/latex/macro_preprocessor'
require 'asciidoctor/latex/dollar'
require 'asciidoctor/latex/tex_postprocessor'
require 'asciidoctor/latex/chem'
require 'asciidoctor/latex/sectnumoffset-treeprocessor'
# require 'asciidoctor/latex/preamble_processor'
$VERBOSE = false
module Asciidoctor::LaTeX
# code for Html5ConverterExtension & its insertion
# template by @mojavelinux
module Html5ConverterExtensions
ENV_CSS_OBLIQUE = "+++
+++"
ENV_CSS_PLAIN = "+++
+++"
DIV_END = '+++
+++'
TABLE_ROW_END = '++++++'
def info node
warn "\n HTMLConverter, node: #{node.node_name}".red if $VERBOSE
end
# Dispatches a handler for the _node_ (`NODE`)
# based on its role.
def environment node
warn "entering environment(node)".red if $VERBOSE
attrs = node.attributes
warn "attrs['role'] = #{attrs['role']}".blue if $VERBOSE
case attrs['role']
when 'box', 'capsule'
handle_null(node)
when 'code'
handle_code(node)
when 'equation'
handle_equation(node)
when 'equationalign'
handle_equation_align(node)
when 'cd'
handle_cd(node)
when 'chem'
handle_chem(node)
when 'jsxgraph'
handle_jsxgraph(node)
when 'texmacro'
handle_texmacro(node)
when 'include_latex'
handle_include_latex(node)
else
handle_default(node)
end
node.attributes['roles'] = (node.roles + ['environment']) * ' '
self.open node
end # method environment
def environment_literal node
warn "entering environment(node)".red if $VERBOSE
attrs = node.attributes
warn "attrs['role'] = #{attrs['role']}".blue if $VERBOSE
case attrs['role']
when 'box', 'capsule'
handle_null(node)
when 'code'
handle_code(node)
when 'equation'
handle_equation_literal(node)
when 'equationalign'
handle_equation_align(node)
when 'cd'
handle_cd(node)
when 'chem'
handle_chem(node)
when 'jsxgraph'
handle_jsxgraph(node)
when 'texmacro'
handle_texmacro(node)
else
handle_default(node)
end
node.attributes['roles'] = (node.roles + ['environment']) * ' '
self.open node
end # method environment_literal
def handle_texmacro node
node.title = nil
node.lines = ["+++\n\\("] + node.lines + ["\\)\n+++"]
end
# Example:
# [env.include_latex]
# --
# \input abc.text
# \usepackage{def}
# --
# Nothing appears in the HTML,
# bu lines
# \input abc.text
# \usepackage{def}
# appear in the generated tex file.
def handle_include_latex node
node.title = nil
node.lines = [] # ["// "] + node.lines
self.open node
end
def click node
if node.attributes['plain-option']
node.lines = [ENV_CSS_PLAIN] + node.lines + [DIV_END]
else
node.lines = [ENV_CSS_OBLIQUE] + node.lines + [DIV_END]
end
node.attributes['roles'] = (node.roles + ['click']) * ' '
self.open node
end
def old_inline_anchor node
target = node.target
case node.type
when :xref
refid = node.attributes['refid'] || target
# NOTE we lookup text in converter because DocBook doesn't need this logic
text = node.text || (node.document.references[:ids][refid] || %([#{refid}]))
# FIXME shouldn't target be refid? logic seems confused here
%(#{text})
when :ref
%()
when :link
attrs = []
attrs << %( id="#{node.id}") if node.id
if (role = node.role)
attrs << %( class="#{role}")
end
attrs << %( title="#{node.attr 'title'}") if node.attr? 'title', nil, false
attrs << %( target="#{node.attr 'window'}") if node.attr? 'window', nil, false
%(#{node.text})
when :bibref
%([#{target}])
else
warn %(asciidoctor: WARNING: unknown anchor type: #{node.type.inspect})
end
end
def inline_anchor node
case node.type.to_s
when 'xref'
refid = node.attributes['refid']
if refid and refid[0] == '_'
output = "#{refid.gsub('_',' ')}"
else
refs = node.parent.document.references[:ids]
# FIXME: the next line is HACKISH (and it crashes the app when refs[refid]) is nil)
# FIXME: and with the fix for nil results is even more hackish
if refs[refid]
reftext = refs[refid].gsub('.', '')
reftext = reftext.gsub(/:.*/,'')
if refid =~ /\Aeq-/
output = "equation #{reftext}"
elsif refid =~ /\Aformula-/
output = "formula #{reftext}"
elsif refid =~ /\Areaction-/
output = "reaction #{reftext}"
else
output = "#{reftext}"
end
else
output = old_inline_anchor node
end
end
when 'link'
output = "#{node.text}"
else
output = old_inline_anchor node
end
output
end
def handle_equation(node)
attrs = node.attributes
options = attrs['options']
node.title = nil
number_part = '
+++']
end
def handle_jsxgraph(node)
attrs = node.attributes
if attrs['box'] == nil
attrs['box'] = 'box'
end
if attrs['width'] == nil
attrs['width'] = 450
end
if attrs['height'] == nil
attrs['height'] = 450
end
line_array = ["\n+++\n"]
# line_array += [""]
line_array += [""]
line_array += ['']
line_array += [""]
line_array += [""]
line_array += ['']
line_array += [' ']
line_array += ["\n+++\n"]
node.lines = line_array
end
def handle_null(node)
end
def handle_code(node)
warn "handle code".red if $VERBOSE
if node.attributes['id'].nil?
node.title = 'FOO'
node.caption = '666'
end
end
def handle_default(node)
if node.attributes['plain-option']
node.lines = [ENV_CSS_PLAIN] + node.lines + [DIV_END]
else
node.lines = [ENV_CSS_OBLIQUE] + node.lines + [DIV_END]
end
end
end
# END OF Html5ConverterExtensions
class Converter
include Asciidoctor::Converter
register_for 'latex'
# puts "HOLA!".red
# Note: invoke asciidoctor-latex by
#
# asciidoctor-latex -a dialect=asciidoc foo.adoc
# asciidoctor-latex -a dialect=manuscript foo.adoc
# asciidoctor-latex -a dialect=latex foo.adoc
#
# These are source file options: for plain asciidoc,
# asciidoc-manuscript, and asciidoc-latex
Asciidoctor::Extensions.register do
dialect = document.options['dialect'] || document.attributes['dialect'] || 'latex'
# puts "DIALECT = #{dialect}".red
if ['asciidoc', 'manuscript'].include? dialect
preprocessor DollarPreprocessor if document.basebackend? 'tex'
end
if ['latex', 'manuscript'].include? dialect
preprocessor ClickStyleInsert if document.attributes['css_extras'] == 'include'
preprocessor MacroPreprocessor
block EnvironmentBlock
block EnvironmentBlock2
block ClickBlock
inline_macro GlossInlineMacro
inline_macro IndexTermInlineMacro
postprocessor InjectHTML unless document.attributes['inject_javascript'] == 'no'
postprocessor EntToUni if document.basebackend? 'tex' unless document.attributes['unicode'] == 'no'
docinfo_processor CSSDocinfoProcessor
end
if ['latex'].include? dialect
preprocessor TeXPreprocessor unless document.attributes['preprocess'] == 'no'
preprocessor MacroInsert if (File.exist? 'macros.tex')
inline_macro ChemInlineMacro
block_macro IncludeLatexBlockMacro
postprocessor Chem if document.basebackend? 'html'
postprocessor HTMLPostprocessor if document.basebackend? 'html'
postprocessor TexPostprocessor if document.basebackend? 'tex'
end
end
Asciidoctor::Extensions.register :latex do
# EnvironmentBlock
end
TOP_TYPES = %w(document section)
LIST_TYPES = %w(dlist olist ulist)
INLINE_TYPES = %w(inline_anchor inline_break inline_footnote inline_quoted inline_callout)
BLOCK_TYPES = %w(admonition listing literal page_break paragraph stem pass open quote \
example floating_title image click preamble sidebar verse toc)
OTHER_TYPES = %w(environment table)
NODE_TYPES = TOP_TYPES + LIST_TYPES + INLINE_TYPES + BLOCK_TYPES + OTHER_TYPES
def initialize backend, opts
super
basebackend 'tex'
outfilesuffix '.tex'
end
# FIXME: find a solution without a global variable
$latex_environment_names = []
# FIXME: this should be retired -- still used
# in click blocks but no longer in env blocks
$label_counter = 0
def convert node, transform = nil
if NODE_TYPES.include? node.node_name
node.tex_process
else
warn %(Node to implement: #{node.node_name}, class = #{node.class}).magenta # if $VERBOSE
# This warning should not be switched off by $VERBOSE
end
end
end # class Converter
end # module Asciidoctor::LaTeX
class Asciidoctor::Converter::Html5Converter
# inject our custom code into the existing Html5Converter class (Ruby 2.0 and above)
# the ideal is to use 'prepend'; however, this is incompatible
# with the current version of Opal, so the alternative of 'include' is provided for
# cases in which 'prepend' is not available
if respond_to? :prepend
prepend Asciidoctor::LaTeX::Html5ConverterExtensions
else
include Asciidoctor::LaTeX::Html5ConverterExtensions
end
# include Asciidoctor::LaTeX::Html5ConverterExtensions
end