# encoding: utf-8
#
# Copyright (c) 2002-2007 Minero Aoki
# 2008-2009 Minero Aoki, Kenshi Muto
# 2010 Minero Aoki, Kenshi Muto, TAKAHASHI Masayoshi
#
# This program is free software.
# You can distribute or modify this program under the terms of
# the GNU LGPL, Lesser General Public License version 2.1.
# For details of the GNU LGPL, see the file "COPYING".
#
require 'review/builder'
require 'review/latexutils'
require 'review/textutils'
module ReVIEW
class LATEXBuilder < Builder
include LaTeXUtils
include TextUtils
[:dtp, :hd_chap].each {|e|
Compiler.definline(e)
}
Compiler.defblock(:memo, 0..1)
Compiler.defsingle(:latextsize, 1)
def extname
'.tex'
end
def builder_init_file
@blank_needed = false
@latex_tsize = nil
@tsize = nil
@table_caption = nil
@ol_num = nil
@sec_counter = SecCounter.new(5, @chapter)
end
private :builder_init_file
def blank
@blank_needed = true
end
private :blank
def print(*s)
if @blank_needed
@output.puts
@blank_needed = false
end
super
end
private :print
def puts(*s)
if @blank_needed
@output.puts
@blank_needed = false
end
super
end
private :puts
HEADLINE = {
1 => 'chapter',
2 => 'section',
3 => 'subsection',
4 => 'subsubsection',
5 => 'paragraph',
6 => 'subparagraph'
}
def headline(level, label, caption)
_, anchor = headline_prefix(level)
headline_name = HEADLINE[level]
if @chapter.kind_of? ReVIEW::Book::Part
headline_name = "part"
end
prefix = ""
if level > @book.config["secnolevel"] || (@chapter.number.to_s.empty? && level > 1)
prefix = "*"
end
blank unless @output.pos == 0
puts macro(headline_name+prefix, compile_inline(caption))
if prefix == "*" && level <= @book.config["toclevel"].to_i
puts "\\addcontentsline{toc}{#{headline_name}}{#{compile_inline(caption)}}"
end
if level == 1
puts macro('label', chapter_label)
else
puts macro('label', sec_label(anchor))
end
rescue
error "unknown level: #{level}"
end
def nonum_begin(level, label, caption)
blank unless @output.pos == 0
puts macro(HEADLINE[level]+"*", compile_inline(caption))
end
def nonum_end(level)
end
def column_begin(level, label, caption)
blank
puts "\\begin{reviewcolumn}\n"
if label
puts "\\hypertarget{#{column_label(label)}}{}"
else
puts "\\hypertarget{#{column_label(caption)}}{}"
end
puts macro('reviewcolumnhead', nil, compile_inline(caption))
if level <= @book.config["toclevel"].to_i
puts "\\addcontentsline{toc}{#{HEADLINE[level]}}{#{compile_inline(caption)}}"
end
end
def column_end(level)
puts "\\end{reviewcolumn}\n"
blank
end
def captionblock(type, lines, caption)
puts "\\begin{reviewminicolumn}\n"
unless caption.nil?
puts "\\reviewminicolumntitle{#{compile_inline(caption)}}\n"
end
if @book.config["deprecated-blocklines"].nil?
blocked_lines = split_paragraph(lines)
puts blocked_lines.join("\n\n")
else
lines.each do |line|
puts line
end
end
puts "\\end{reviewminicolumn}\n"
end
def box(lines, caption = nil)
blank
if caption
puts macro('reviewboxcaption', "#{compile_inline(caption)}")
end
puts '\begin{reviewbox}'
lines.each do |line|
puts detab(line)
end
puts '\end{reviewbox}'
blank
end
def ul_begin
blank
puts '\begin{itemize}'
end
def ul_item(lines)
str = lines.join
str.sub!(/\A(\[)/){'\lbrack{}'}
puts '\item ' + str
end
def ul_end
puts '\end{itemize}'
blank
end
def ol_begin
blank
puts '\begin{enumerate}'
if @ol_num
puts "\\setcounter{enumi}{#{@ol_num - 1}}"
@ol_num = nil
end
end
def ol_item(lines, num)
str = lines.join
str.sub!(/\A(\[)/){'\lbrack{}'}
puts '\item ' + str
end
def ol_end
puts '\end{enumerate}'
blank
end
def dl_begin
blank
puts '\begin{description}'
end
def dt(str)
str.sub!(/\[/){'\lbrack{}'}
str.sub!(/\]/){'\rbrack{}'}
puts '\item[' + str + '] \mbox{} \\\\'
end
def dd(lines)
puts lines.join
end
def dl_end
puts '\end{description}'
blank
end
def paragraph(lines)
blank
lines.each do |line|
puts line
end
blank
end
def parasep
puts '\\parasep'
end
def read(lines)
latex_block 'quotation', lines
end
alias_method :lead, :read
def highlight_listings?
@book.config["highlight"] && @book.config["highlight"]["latex"] == "listings"
end
private :highlight_listings?
def emlist(lines, caption = nil, lang = nil)
blank
if highlight_listings?
common_code_block_lst(lines, 'reviewemlistlst', 'title', caption, lang)
else
common_code_block(lines, 'reviewemlist', caption, lang) do |line, idx|
detab(line) + "\n"
end
end
end
def emlistnum(lines, caption = nil, lang = nil)
blank
if highlight_listings?
common_code_block_lst(lines, 'reviewemlistnumlst', 'title', caption, lang)
else
common_code_block(lines, 'reviewemlist', caption, lang) do |line, idx|
detab((idx+1).to_s.rjust(2)+": " + line) + "\n"
end
end
end
## override Builder#list
def list(lines, id, caption, lang = nil)
if highlight_listings?
common_code_block_lst(lines, 'reviewlistlst', 'caption', caption, lang)
else
begin
puts macro('reviewlistcaption', "#{I18n.t("list")}#{I18n.t("format_number_header", [@chapter.number, @chapter.list(id).number])}#{I18n.t("caption_prefix")}#{compile_inline(caption)}")
rescue KeyError
error "no such list: #{id}"
end
common_code_block(lines, 'reviewlist', nil, lang) do |line, idx|
detab(line) + "\n"
end
end
end
## override Builder#listnum
def listnum(lines, id, caption, lang = nil)
if highlight_listings?
common_code_block_lst(lines, 'reviewlistnumlst', 'caption', caption, lang)
else
begin
puts macro('reviewlistcaption', "#{I18n.t("list")}#{I18n.t("format_number_header", [@chapter.number, @chapter.list(id).number])}#{I18n.t("caption_prefix")}#{compile_inline(caption)}")
rescue KeyError
error "no such list: #{id}"
end
common_code_block(lines, 'reviewlist', caption, lang) do |line, idx|
detab((idx+1).to_s.rjust(2)+": " + line) + "\n"
end
end
end
def cmd(lines, caption = nil, lang = nil)
if highlight_listings?
common_code_block_lst(lines, 'reviewcmdlst', 'title', caption, lang)
else
blank
common_code_block(lines, 'reviewcmd', caption, lang) do |line, idx|
detab(line) + "\n"
end
end
end
def common_code_block(lines, command, caption, lang)
if caption
puts macro(command + 'caption', "#{compile_inline(caption)}")
end
body = ""
lines.each_with_index do |line, idx|
body.concat(yield(line, idx))
end
puts macro('begin' ,command)
print body
puts macro('end' ,command)
blank
end
def common_code_block_lst(lines, command, title, caption, lang)
caption_str = compile_inline((caption || ""))
if title == "title" && caption_str == ""
caption_str = "\\relax" ## dummy charactor to remove lstname
print "\\vspace{-1.5em}"
end
lexer = lang || ""
body = lines.inject(''){|i, j| i + detab(unescape_latex(j)) + "\n"}
puts "\\begin{"+command+"}["+title+"={"+caption_str+"},language={"+ lexer+"}]"
print body
puts "\\end{"+ command + "}"
blank
end
def source(lines, caption)
puts '\begin{reviewlist}'
puts macro('reviewlistcaption', compile_inline(caption))
lines.each do |line|
puts detab(line)
end
puts '\end{reviewlist}'
puts ""
end
def image_header(id, caption)
end
def result_metric(array)
"#{array.join(',')}"
end
def image_image(id, caption, metric)
metrics = parse_metric("latex", metric)
# image is always bound here
puts '\begin{reviewimage}'
if metrics.present?
puts "\\includegraphics[#{metrics}]{#{@chapter.image(id).path}}"
else
puts "\\includegraphics[width=\\maxwidth]{#{@chapter.image(id).path}}"
end
if caption.present?
puts macro('caption', compile_inline(caption))
end
puts macro('label', image_label(id))
puts '\end{reviewimage}'
end
def image_dummy(id, caption, lines)
puts '\begin{reviewdummyimage}'
path = @chapter.image(id).path
puts "--[[path = #{path} (#{existence(id)})]]--"
lines.each do |line|
puts detab(line.rstrip)
end
puts macro('label', image_label(id))
puts compile_inline(caption)
puts '\end{reviewdummyimage}'
end
def existence(id)
@chapter.image(id).bound? ? 'exist' : 'not exist'
end
private :existence
def image_label(id, chapter=nil)
chapter ||= @chapter
"image:#{chapter.id}:#{id}"
end
private :image_label
def chapter_label
"chap:#{@chapter.id}"
end
private :chapter_label
def sec_label(sec_anchor)
"sec:#{sec_anchor}"
end
private :sec_label
def table_label(id, chapter=nil)
chapter ||= @chapter
"table:#{chapter.id}:#{id}"
end
private :table_label
def bib_label(id)
"bib:#{id}"
end
private :bib_label
def column_label(id)
filename = @chapter.id
num = @chapter.column(id).number
"column:#{filename}:#{num}"
end
private :column_label
def indepimage(id, caption=nil, metric=nil)
metrics = parse_metric("latex", metric)
puts '\begin{reviewimage}'
if metrics.present?
puts "\\includegraphics[#{metrics}]{#{@chapter.image(id).path}}"
else
puts "\\includegraphics[width=\\maxwidth]{#{@chapter.image(id).path}}"
end
if caption.present?
puts macro('reviewindepimagecaption',
%Q[#{I18n.t("numberless_image")}#{I18n.t("caption_prefix")}#{compile_inline(caption)}])
end
puts '\end{reviewimage}'
end
alias_method :numberlessimage, :indepimage
def table(lines, id = nil, caption = nil)
rows = []
sepidx = nil
lines.each_with_index do |line, idx|
if /\A[\=\{\-\}]{12}/ =~ line
# just ignore
#error "too many table separator" if sepidx
sepidx ||= idx
next
end
rows.push line.strip.split(/\t+/).map {|s| s.sub(/\A\./, '') }
end
rows = adjust_n_cols(rows)
begin
table_header id, caption unless caption.nil?
rescue KeyError
error "no such table: #{id}"
end
return if rows.empty?
table_begin rows.first.size
if sepidx
sepidx.times do
tr rows.shift.map {|s| th(s) }
end
rows.each do |cols|
tr cols.map {|s| td(s) }
end
else
rows.each do |cols|
h, *cs = *cols
tr [th(h)] + cs.map {|s| td(s) }
end
end
table_end
end
def table_header(id, caption)
if caption.present?
@table_caption = true
puts '\begin{table}[h]'
puts macro('reviewtablecaption', compile_inline(caption))
end
puts macro('label', table_label(id))
end
def table_begin(ncols)
if @latex_tsize
puts macro('begin', 'reviewtable', @latex_tsize)
elsif @tsize
cellwidth = @tsize.split(/\s*,\s*/)
puts macro('begin', 'reviewtable', '|'+(cellwidth.collect{|i| "p{#{i}mm}"}.join('|'))+'|')
else
puts macro('begin', 'reviewtable', (['|'] * (ncols + 1)).join('l'))
end
puts '\hline'
@tsize = nil
@latex_tsize = nil
end
def table_separator
#puts '\hline'
end
def th(s)
## use shortstack for @
if /\\\\/i =~ s
macro('reviewth', macro('shortstack[l]', s))
else
macro('reviewth', s)
end
end
def td(s)
## use shortstack for @
if /\\\\/ =~ s
macro('shortstack[l]', s)
else
s
end
end
def tr(rows)
print rows.join(' & ')
puts ' \\\\ \hline'
end
def table_end
puts macro('end', 'reviewtable')
if @table_caption
puts '\end{table}'
end
@table_caption = nil
blank
end
def quote(lines)
latex_block 'quote', lines
end
def center(lines)
latex_block 'center', lines
end
alias_method :centering, :center
def flushright(lines)
latex_block 'flushright', lines
end
def texequation(lines)
blank
puts macro('begin','equation*')
lines.each do |line|
puts unescape_latex(line)
end
puts macro('end', 'equation*')
blank
end
def latex_block(type, lines)
blank
puts macro('begin', type)
if @book.config["deprecated-blocklines"].nil?
blocked_lines = split_paragraph(lines)
puts blocked_lines.join("\n\n")
else
lines.each do |line|
puts line
end
end
puts macro('end', type)
blank
end
private :latex_block
def direct(lines, fmt)
return unless fmt == 'latex'
lines.each do |line|
puts line
end
end
def comment(lines, comment = nil)
lines ||= []
lines.unshift comment unless comment.blank?
if @book.config["draft"]
str = lines.join("")
puts macro('pdfcomment', escape(str))
end
end
def hr
puts '\hrule'
end
def label(id)
puts macro('label', id)
end
def pagebreak
puts '\pagebreak'
end
def linebreak
puts '\\\\'
end
def noindent
print '\noindent'
end
def inline_chapref(id)
title = super
if @book.config["chapterlink"]
"\\hyperref[chap:#{id}]{#{title}}"
else
title
end
rescue KeyError
error "unknown chapter: #{id}"
nofunc_text("[UnknownChapter:#{id}]")
end
def inline_chap(id)
if @book.config["chapterlink"]
"\\hyperref[chap:#{id}]{#{@book.chapter_index.number(id)}}"
else
@book.chapter_index.number(id)
end
rescue KeyError
error "unknown chapter: #{id}"
nofunc_text("[UnknownChapter:#{id}]")
end
def inline_title(id)
title = super
if @book.config["chapterlink"]
"\\hyperref[chap:#{id}]{#{title}}"
else
title
end
rescue KeyError
error "unknown chapter: #{id}"
nofunc_text("[UnknownChapter:#{id}]")
end
# FIXME: use TeX native label/ref.
def inline_list(id)
chapter, id = extract_chapter_id(id)
macro('reviewlistref', "#{chapter.number}.#{chapter.list(id).number}")
end
def inline_table(id)
chapter, id = extract_chapter_id(id)
macro('reviewtableref', "#{chapter.number}.#{chapter.table(id).number}", table_label(id, chapter))
end
def inline_img(id)
chapter, id = extract_chapter_id(id)
macro('reviewimageref', "#{chapter.number}.#{chapter.image(id).number}", image_label(id, chapter))
end
def footnote(id, content)
if @book.config["footnotetext"]
puts macro("footnotetext[#{@chapter.footnote(id).number}]",
compile_inline(content.strip))
end
end
def inline_fn(id)
if @book.config["footnotetext"]
macro("footnotemark[#{@chapter.footnote(id).number}]", "")
else
macro('footnote', compile_inline(@chapter.footnote(id).content.strip))
end
end
BOUTEN = "・"
def inline_bou(str)
str.split(//).map {|c| macro('ruby', escape(c), macro('textgt', BOUTEN)) }.join('\allowbreak')
end
def compile_ruby(base, ruby)
macro('ruby', escape(base), escape(ruby))
end
# math
def inline_m(str)
" $#{str}$ "
end
# hidden index
def inline_hi(str)
index(str)
end
# index -> italic
def inline_i(str)
macro('textit', escape(str))
end
# index
def inline_idx(str)
escape(str) + index(str)
end
# hidden index??
def inline_hidx(str)
index(str)
end
# bold
def inline_b(str)
macro('textbf', escape(str))
end
# line break
def inline_br(str)
"\\\\\n"
end
def inline_dtp(str)
# ignore
""
end
## @ is same as @
def inline_code(str)
macro('texttt', escape(str))
end
def nofunc_text(str)
escape(str)
end
def inline_tt(str)
macro('texttt', escape(str))
end
def inline_del(str)
macro('reviewstrike', escape(str))
end
def inline_tti(str)
macro('texttt', macro('textit', escape(str)))
end
def inline_ttb(str)
macro('texttt', macro('textbf', escape(str)))
end
def inline_bib(id)
macro('reviewbibref', "[#{@chapter.bibpaper(id).number}]", bib_label(id))
end
def inline_hd_chap(chap, id)
n = chap.headline_index.number(id)
if chap.number and @book.config["secnolevel"] >= n.split('.').size
str = "「#{chap.headline_index.number(id)} #{compile_inline(chap.headline(id).caption)}」"
else
str = "「#{compile_inline(chap.headline(id).caption)}」"
end
if @book.config["chapterlink"]
anchor = n.gsub(/\./, "-")
macro('reviewsecref', str, sec_label(anchor))
else
str
end
end
def inline_column(id)
macro('reviewcolumnref', "#{@chapter.column(id).caption}", column_label(id))
end
def inline_raw(str)
super(str)
end
def inline_sub(str)
macro('textsubscript', escape(str))
end
def inline_sup(str)
macro('textsuperscript', escape(str))
end
def inline_em(str)
macro('reviewem', escape(str))
end
def inline_strong(str)
macro('reviewstrong', escape(str))
end
def inline_u(str)
macro('Underline', escape(str))
end
def inline_ami(str)
macro('reviewami', escape(str))
end
def inline_icon(id)
macro('includegraphics', @chapter.image(id).path)
end
def inline_uchar(str)
# with otf package
macro('UTF', escape(str))
end
def inline_comment(str)
if @book.config["draft"]
macro('pdfcomment', escape(str))
else
""
end
end
def bibpaper_header(id, caption)
puts "[#{@chapter.bibpaper(id).number}] #{compile_inline(caption)}"
puts macro('label', bib_label(id))
end
def bibpaper_bibpaper(id, caption, lines)
print split_paragraph(lines).join("")
puts ""
end
def index(str)
str.sub!(/\(\)/, '')
decl = ''
if /@\z/ =~ str
str.chop!
decl = '|IndexDecl'
end
unless /[^ -~]/ =~ str
if /\^/ =~ str
macro('index', escape_index(str.gsub(/\^/, '')) + '@' + escape_index(text(str)) + decl)
else
'\index{' + escape_index(text(str)) + decl + '}'
end
else
'\index{' + escape_index(@index_db[str]) + '@' + escape_index(text(str)) + '}'
end
end
def compile_kw(word, alt)
if alt
macro('reviewkw', escape(word)) + "(#{escape(alt.strip)})"
else
macro('reviewkw', escape(word))
end
end
def compile_href(url, label)
if /\A[a-z]+:/ =~ url
if label
macro("href", escape_url(url), escape(label))
else
macro("url", escape_url(url))
end
else
macro("ref", url)
end
end
def tsize(str)
@tsize = str
end
def latextsize(str)
@latex_tsize = str
end
def image_ext
"pdf"
end
def olnum(num)
@ol_num = num.to_i
end
end
end