# 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 blank_reset @blank_needed = false end private :blank_reset HEADLINE = { 1 => 'chapter', 2 => 'section', 3 => 'subsection', 4 => 'subsubsection', 5 => 'paragraph', 6 => 'subparagraph' } def headline(level, label, caption) buf = "" _, 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 buf << macro(headline_name+prefix, caption) << "\n" if prefix == "*" && level <= @book.config["toclevel"].to_i buf << "\\addcontentsline{toc}{#{headline_name}}{#{caption}}\n" end if level == 1 buf << macro('label', chapter_label) << "\n" else buf << macro('label', sec_label(anchor)) << "\n" end buf rescue error "unknown level: #{level}" end def nonum_begin(level, label, caption) "\n" + macro(HEADLINE[level]+"*", caption) + "\n" end def nonum_end(level) end def column_begin(level, label, caption) buf = "" blank if @blank_needed buf << "\n" blank_reset end buf << "\\begin{reviewcolumn}\n" if label buf << "\\hypertarget{#{column_label(label)}}{}\n" else buf << "\\hypertarget{#{column_label(caption)}}{}\n" end buf << macro('reviewcolumnhead', nil, caption) << "\n" if level <= @book.config["toclevel"].to_i buf << "\\addcontentsline{toc}{#{HEADLINE[level]}}{#{caption}}" << "\n" end buf end def column_end(level) buf = "" buf << "\\end{reviewcolumn}\n" blank buf end def captionblock(type, lines, caption) buf = "" buf << "\\begin{reviewminicolumn}\n" unless caption.nil? buf << "\\reviewminicolumntitle{#{caption}}" end if @book.config["deprecated-blocklines"].nil? buf << lines.join("") else error "deprecated-blocklines is obsoleted." end buf << "\\end{reviewminicolumn}\n" buf end def box(lines, caption = nil) buf = "\n" if caption buf << macro('reviewboxcaption', "#{caption}") << "\n" end buf << '\begin{reviewbox}' << "\n" lines.each do |line| buf << detab(line) << "\n" end< buf << '\end{reviewbox}' << "\n" end def ul_begin buf = "\n" buf << '\begin{itemize}' << "\n" buf end def ul_item(lines) str = lines.join str.sub!(/\A(\[)/){'\lbrack{}'} '\item ' + str + "\n" end def ul_end '\end{itemize}' + "\n" end def ol_begin buf = "\n" buf << '\begin{enumerate}' << "\n" if @ol_num buf << "\\setcounter{enumi}{#{@ol_num - 1}}\n" @ol_num = nil end buf end def ol_item(lines, num) str = lines.join str.sub!(/\A(\[)/){'\lbrack{}'} '\item ' + str + "\n" end def ol_end '\end{enumerate}' + "\n" end def dl_begin "\n" + '\begin{description}' + "\n" end def dt(str) str.sub!(/\[/){'\lbrack{}'} str.sub!(/\]/){'\rbrack{}'} '\item[' + str + '] \mbox{} \\\\' + "\n" end def dd(lines) lines.join + "\n" end def dl_end '\end{description}' + "\n" end def paragraph(lines) buf = "\n" lines.each do |line| buf << line end buf << "\n" buf end def parasep '\\parasep' + "\n" 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) buf = "\n" if highlight_listings? buf << common_code_block_lst(lines, 'reviewemlistlst', 'title', caption, lang) else buf << common_code_block(lines, 'reviewemlist', caption, lang) do |line, idx| detab(line) + "\n" end end buf end def emlistnum(lines, caption = nil, lang = nil) buf = "\n" if highlight_listings? buf << common_code_block_lst(lines, 'reviewemlistnumlst', 'title', caption, lang) else buf << common_code_block(lines, 'reviewemlist', caption, lang) do |line, idx| detab((idx+1).to_s.rjust(2)+": " + line) + "\n" end end buf end ## override Builder#list def list(lines, id, caption = nil, lang = nil) buf = "" if highlight_listings? buf << common_code_block_lst(lines, 'reviewlistlst', 'caption', caption, lang) else begin buf << macro('reviewlistcaption', "#{I18n.t("list")}#{I18n.t("format_number_header", [@chapter.number, @chapter.list(id).number])}#{I18n.t("caption_prefix")}#{caption}") + "\n" rescue KeyError error "no such list: #{id}" end buf << common_code_block(lines, 'reviewlist', nil, lang) do |line, idx| detab(line) + "\n" end end buf end ## override Builder#listnum def listnum(lines, id, caption = nil, lang = nil) buf = "" if highlight_listings? buf << common_code_block_lst(lines, 'reviewlistnumlst', 'caption', caption, lang) else begin buf << macro('reviewlistcaption', "#{I18n.t("list")}#{I18n.t("format_number_header", [@chapter.number, @chapter.list(id).number])}#{I18n.t("caption_prefix")}#{caption}") + "\n" rescue KeyError error "no such list: #{id}" end buf << common_code_block(lines, 'reviewlist', caption, lang) do |line, idx| detab((idx+1).to_s.rjust(2)+": " + line) + "\n" end end buf end def cmd(lines, caption = nil, lang = nil) buf = "" if highlight_listings? buf << common_code_block_lst(lines, 'reviewcmdlst', 'title', caption, lang) else buf << "\n" buf << common_code_block(lines, 'reviewcmd', caption, lang) do |line, idx| detab(line) + "\n" end end buf end def common_code_block(lines, command, caption, lang) buf = "" if caption buf << macro(command + 'caption', "#{caption}") + "\n" end body = "" lines.each_with_index do |line, idx| body.concat(yield(line, idx)) end buf << macro('begin' ,command) + "\n" buf << body buf << macro('end' ,command) + "\n" buf end def common_code_block_lst(lines, command, title, caption, lang) buf = "" caption_str = (caption || "") if title == "title" && caption_str == "" caption_str = "\\relax" ## dummy charactor to remove lstname buf << "\\vspace{-1.5em}" end if @book.config["highlight"] && @book.config["highlight"]["lang"] lexer = @book.config["highlight"]["lang"] # default setting else lexer = "" end lexer = lang if lang.present? body = lines.inject(''){|i, j| i + detab(unescape_latex(j)) + "\n"} buf << "\\begin{"+command+"}["+title+"={"+caption_str+"},language={"+ lexer+"}]" + "\n" buf << body buf << "\\end{"+ command + "}" + "\n" buf end def source(lines, caption = nil) buf = "\n" buf << '\begin{reviewlist}' << "\n" buf << source_header(caption) buf << source_body(lines) buf << '\end{reviewlist}' << "\n" buf << "\n" buf end def source_header(caption) macro('reviewlistcaption', caption) + "\n" end def source_body(lines) buf = "" lines.each do |line| buf << detab(line) << "\n" end buf end def image_header(id, caption) end def result_metric(array) "#{array.join(',')}" end def image_image(id, caption, metric) buf = "" metrics = parse_metric("latex", metric) # image is always bound here buf << '\begin{reviewimage}' << "\n" if metrics.present? buf << "\\includegraphics[#{metrics}]{#{@chapter.image(id).path}}\n" else buf << "\\includegraphics[width=\\maxwidth]{#{@chapter.image(id).path}}\n" end if caption.present? buf << macro('caption', caption) << "\n" end buf << macro('label', image_label(id)) << "\n" buf << '\end{reviewimage}' << "\n" buf end def image_dummy(id, caption, lines) buf << '\begin{reviewdummyimage}' << "\n" path = @chapter.image(id).path buf << "--[[path = #{path} (#{existence(id)})]]--\n" lines.each do |line| buf << detab(line.rstrip) << "\n" end buf << macro('label', image_label(id)) << "\n" buf << caption << "\n" buf << '\end{reviewdummyimage}' << "\n" buf 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) buf = "" metrics = parse_metric("latex", metric) buf << '\begin{reviewimage}' << "\n" if metrics.present? buf << "\\includegraphics[#{metrics}]{#{@chapter.image(id).path}}\n" else buf << "\\includegraphics[width=\\maxwidth]{#{@chapter.image(id).path}}\n" end if caption.present? buf << macro('reviewindepimagecaption', %Q[#{I18n.t("numberless_image")}#{I18n.t("caption_prefix")}#{caption}]) << "\n" end buf << '\end{reviewimage}' << "\n" buf end alias_method :numberlessimage, :indepimage def table(lines, id = nil, caption = nil) buf = "" 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 buf << table_header(id, caption) unless caption.nil? rescue KeyError error "no such table: #{id}" end return buf if rows.empty? buf << table_begin(rows.first.size) if sepidx sepidx.times do buf << tr(rows.shift.map {|s| th(s) }) end rows.each do |cols| buf << tr(cols.map {|s| td(s) }) end else rows.each do |cols| h, *cs = *cols buf << tr([th(h)] + cs.map {|s| td(s) }) end end buf << table_end buf end def table_header(id, caption) buf = "" if caption.present? @table_caption = true buf << '\begin{table}[h]' << "\n" buf << macro('reviewtablecaption', caption) << "\n" end buf << macro('label', table_label(id)) << "\n" buf end def table_begin(ncols) buf = "" if @latex_tsize buf << macro('begin', 'reviewtable', @latex_tsize) << "\n" elsif @tsize cellwidth = @tsize.split(/\s*,\s*/) buf << macro('begin', 'reviewtable', '|'+(cellwidth.collect{|i| "p{#{i}mm}"}.join('|'))+'|') << "\n" else buf << macro('begin', 'reviewtable', (['|'] * (ncols + 1)).join('l')) << "\n" end buf << '\hline' << "\n" @tsize = nil @latex_tsize = nil buf 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) buf = "" buf << rows.join(' & ') << "\n" buf << ' \\\\ \hline' << "\n" buf end def table_end buf = "" buf << macro('end', 'reviewtable') << "\n" if @table_caption buf << '\end{table}' << "\n" end @table_caption = nil buf << "\n" buf 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) buf = "\n" buf << macro('begin','equation*') << "\n" lines.each do |line| buf << unescape_latex(line) << "\n" end buf << macro('end', 'equation*') << "\n" buf << "\n" buf end def latex_block(type, lines) buf = "\n" buf << macro('begin', type) if @book.config["deprecated-blocklines"].nil? buf << lines.join("") else error "deprecated-blocklines is obsoleted." end buf << macro('end', type) << "\n" buf end private :latex_block def direct(lines, fmt) buf = "" return buf unless fmt == 'latex' lines.each do |line| buf << line << "\n" end buf end def comment(lines, comment = nil) buf = "" lines ||= [] lines.unshift comment unless comment.blank? if @book.config["draft"] str = lines.join("") buf << macro('pdfcomment', str) << "\n" end buf end def hr '\hrule' + "\n" end def label(id) macro('label', id) + "\n" end def node_label(node) id = node.args[0].to_raw macro('label', id) + "\n" end def pagebreak '\pagebreak' + "\n" end def linebreak '\\\\' + "\n" end def noindent '\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"] macro("footnotetext[#{@chapter.footnote(id).number}]", content.strip) + "\n" end end def inline_fn(id) if @book.config["footnotetext"] macro("footnotemark[#{@chapter.footnote(id).number}]", "") else macro('footnote', escape(@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', base, ruby) end # math # def inline_m(str) # " $#{str}$ " # end def node_inline_m(node) " $#{node[0].to_raw}$ " end # hidden index def inline_hi(str) index(str) end # index -> italic def inline_i(str) macro('textit', str) end # index def inline_idx(str) escape(str) + index(str) end def node_inline_idx(nodelist) content = nodelist[0].to_raw escape(content) + index(content) end # hidden index?? def inline_hidx(str) index(str) end def node_inline_hidx(nodelist) content = nodelist[0].to_raw index(content) end # bold def inline_b(str) macro('textbf', 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', str) end def nofunc_text(str) escape(str) end def inline_tt(str) macro('texttt', str) end def inline_del(str) macro('reviewstrike', str) end def inline_tti(str) macro('texttt', macro('textit', str)) end def inline_ttb(str) macro('texttt', macro('textbf', 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 = I18n.t("chapter_quote", "#{chap.headline_index.number(id)} #{chap.headline(id).caption}") else str = I18n.t("chapter_quote", chap.headline(id).caption) end if @book.config["chapterlink"] anchor = n.gsub(/\./, "-") macro('reviewsecref', escape(str), sec_label(anchor)) else escape(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', str) end def inline_sup(str) macro('textsuperscript', str) end def inline_em(str) macro('reviewem', str) end def inline_strong(str) macro('reviewstrong', str) end def inline_u(str) macro('Underline', str) end def inline_ami(str) macro('reviewami', str) end def inline_icon(id) macro('includegraphics', @chapter.image(id).path) end def inline_uchar(str) # with otf package macro('UTF', str) end def inline_comment(str) if @book.config["draft"] macro('pdfcomment', escape(str)) else "" end end def bibpaper(lines, id, caption) buf = "" buf << bibpaper_header(id, caption) if lines.empty? buf << "\n" else buf << "\n" buf << bibpaper_bibpaper(id, caption, lines) end buf << "\n" buf end def bibpaper_header(id, caption) "[#{@chapter.bibpaper(id).number}] #{caption}\n" + macro('label', bib_label(id)) end def bibpaper_bibpaper(id, caption, lines) lines.join("") end def index(str) "\\index{" + str + "}" end def compile_kw(word, alt) if alt macro('reviewkw', word) + "(#{alt.strip})" else macro('reviewkw', word) end end def compile_href(url, label) if /\A[a-z]+:/ =~ url if label macro("href", escape_url(url), 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