Class | SM::ToLaTeX |
In: |
markup/simple_markup/to_latex.rb
|
Parent: | Object |
BS | = | "\020" |
CB | = | "\022" |
DL | = | "\023" |
BACKSLASH | = | "#{BS}symbol#{OB}92#{CB}" |
HAT | = | "#{BS}symbol#{OB}94#{CB}" |
BACKQUOTE | = | "#{BS}symbol#{OB}0#{CB}" |
TILDE | = | "#{DL}#{BS}sim#{DL}" |
LESSTHAN | = | "#{DL}<#{DL}" |
GREATERTHAN | = | "#{DL}>#{DL}" |
LIST_TYPE_TO_LATEX | = | { ListBase::BULLET => [ l("\\begin{itemize}"), l("\\end{itemize}") ], ListBase::NUMBER => [ l("\\begin{enumerate}"), l("\\end{enumerate}"), "\\arabic" ], ListBase::UPPERALPHA => [ l("\\begin{enumerate}"), l("\\end{enumerate}"), "\\Alph" ], ListBase::LOWERALPHA => [ l("\\begin{enumerate}"), l("\\end{enumerate}"), "\\alph" ], ListBase::LABELED => [ l("\\begin{description}"), l("\\end{description}") ], ListBase::NOTE => [ l("\\begin{tabularx}{\\linewidth}{@{} l X @{}}"), l("\\end{tabularx}") ], } |
InlineTag | = | Struct.new(:bit, :on, :off) |
# File markup/simple_markup/to_latex.rb, line 24 24: def self.l(str) 25: str.tr('\\', BS).tr('{', OB).tr('}', CB).tr('$', DL) 26: end
# File markup/simple_markup/to_latex.rb, line 45 45: def initialize 46: init_tags 47: @list_depth = 0 48: @prev_list_types = [] 49: end
Escape a LaTeX string
# File markup/simple_markup/to_latex.rb, line 64 64: def escape(str) 65: # $stderr.print "FE: ", str 66: s = str. 67: # sub(/\s+$/, ''). 68: gsub(/([_\${}&%#])/, "#{BS}\\1"). 69: gsub(/\\/, BACKSLASH). 70: gsub(/\^^/, HAT). 71: gsub(/~/, TILDE). 72: gsub(/</, LESSTHAN). 73: gsub(/>/, GREATERTHAN). 74: gsub(/,,/, ",{},"). 75: gsub(/\`/, BACKQUOTE) 76: # $stderr.print "-> ", s, "\n" 77: s 78: end 79: 80: ## 81: # Add a new set of LaTeX tags for an attribute. We allow 82: # separate start and end tags for flexibility 83: # 84: def add_tag(name, start, stop) 85: @attr_tags << InlineTag.new(SM::Attribute.bitmap_for(name), start, stop) 86: end 87: 88: 89: ## 90: # Here's the client side of the visitor pattern 91: 92: def start_accepting 93: @res = "" 94: @in_list_entry = [] 95: end 96: 97: def end_accepting 98: @res.tr(BS, '\\').tr(OB, '{').tr(CB, '}').tr(DL, '$') 99: end 100: 101: def accept_paragraph(am, fragment) 102: @res << wrap(convert_flow(am.flow(fragment.txt))) 103: @res << "\n" 104: end 105: 106: def accept_verbatim(am, fragment) 107: @res << "\n\\begin{code}\n" 108: @res << fragment.txt.sub(/[\n\s]+\Z/, '') 109: @res << "\n\\end{code}\n\n" 110: end 111: 112: def accept_rule(am, fragment) 113: size = fragment.param 114: size = 10 if size > 10 115: @res << "\n\n\\rule{\\linewidth}{#{size}pt}\n\n" 116: end 117: 118: def accept_list_start(am, fragment) 119: @res << list_name(fragment.type, true) <<"\n" 120: @in_list_entry.push false 121: end 122: 123: def accept_list_end(am, fragment) 124: if tag = @in_list_entry.pop 125: @res << tag << "\n" 126: end 127: @res << list_name(fragment.type, false) <<"\n" 128: end 129: 130: def accept_list_item(am, fragment) 131: if tag = @in_list_entry.last 132: @res << tag << "\n" 133: end 134: @res << list_item_start(am, fragment) 135: @res << wrap(convert_flow(am.flow(fragment.txt))) << "\n" 136: @in_list_entry[-1] = list_end_for(fragment.type) 137: end 138: 139: def accept_blank_line(am, fragment) 140: # @res << "\n" 141: end 142: 143: def accept_heading(am, fragment) 144: @res << convert_heading(fragment.head_level, am.flow(fragment.txt)) 145: end 146: 147: # This is a higher speed (if messier) version of wrap 148: 149: def wrap(txt, line_len = 76) 150: res = "" 151: sp = 0 152: ep = txt.length 153: while sp < ep 154: # scan back for a space 155: p = sp + line_len - 1 156: if p >= ep 157: p = ep 158: else 159: while p > sp and txt[p] != ?\s 160: p -= 1 161: end 162: if p <= sp 163: p = sp + line_len 164: while p < ep and txt[p] != ?\s 165: p += 1 166: end 167: end 168: end 169: res << txt[sp...p] << "\n" 170: sp = p 171: sp += 1 while sp < ep and txt[sp] == ?\s 172: end 173: res 174: end 175: 176: ####################################################################### 177: 178: private 179: 180: ####################################################################### 181: 182: def on_tags(res, item) 183: attr_mask = item.turn_on 184: return if attr_mask.zero? 185: 186: @attr_tags.each do |tag| 187: if attr_mask & tag.bit != 0 188: res << tag.on 189: end 190: end 191: end 192: 193: def off_tags(res, item) 194: attr_mask = item.turn_off 195: return if attr_mask.zero? 196: 197: @attr_tags.reverse_each do |tag| 198: if attr_mask & tag.bit != 0 199: res << tag.off 200: end 201: end 202: end 203: 204: def convert_flow(flow) 205: res = "" 206: flow.each do |item| 207: case item 208: when String 209: # $stderr.puts "Converting '#{item}'" 210: res << convert_string(item) 211: when AttrChanger 212: off_tags(res, item) 213: on_tags(res, item) 214: when Special 215: res << convert_special(item) 216: else 217: raise "Unknown flow element: #{item.inspect}" 218: end 219: end 220: res 221: end 222: 223: # some of these patterns are taken from SmartyPants... 224: 225: def convert_string(item) 226: 227: escape(item). 228: 229: 230: # convert ... to elipsis (and make sure .... becomes .<elipsis>) 231: gsub(/\.\.\.\./, '.\ldots{}').gsub(/\.\.\./, '\ldots{}'). 232: 233: # convert single closing quote 234: gsub(%r{([^ \t\r\n\[\{\(])\'}) { "#$1'" }. 235: gsub(%r{\'(?=\W|s\b)}) { "'" }. 236: 237: # convert single opening quote 238: gsub(/'/, '`'). 239: 240: # convert double closing quote 241: gsub(%r{([^ \t\r\n\[\{\(])\"(?=\W)}) { "#$1''" }. 242: 243: # convert double opening quote 244: gsub(/"/, "``"). 245: 246: # convert copyright 247: gsub(/\(c\)/, '\copyright{}') 248: 249: end 250: 251: def convert_special(special) 252: handled = false 253: Attribute.each_name_of(special.type) do |name| 254: method_name = "handle_special_#{name}" 255: if self.respond_to? method_name 256: special.text = send(method_name, special) 257: handled = true 258: end 259: end 260: raise "Unhandled special: #{special}" unless handled 261: special.text 262: end 263: 264: def convert_heading(level, flow) 265: res = 266: case level 267: when 1 then "\\chapter{" 268: when 2 then "\\section{" 269: when 3 then "\\subsection{" 270: when 4 then "\\subsubsection{" 271: else "\\paragraph{" 272: end + 273: convert_flow(flow) + 274: "}\n" 275: end 276: 277: def list_name(list_type, is_open_tag) 278: tags = LIST_TYPE_TO_LATEX[list_type] || raise("Invalid list type: #{list_type.inspect}") 279: if tags[2] # enumerate 280: if is_open_tag 281: @list_depth += 1 282: if @prev_list_types[@list_depth] != tags[2] 283: case @list_depth 284: when 1 285: roman = "i" 286: when 2 287: roman = "ii" 288: when 3 289: roman = "iii" 290: when 4 291: roman = "iv" 292: else 293: raise("Too deep list: level #{@list_depth}") 294: end 295: @prev_list_types[@list_depth] = tags[2] 296: return l("\\renewcommand{\\labelenum#{roman}}{#{tags[2]}{enum#{roman}}}") + "\n" + tags[0] 297: end 298: else 299: @list_depth -= 1 300: end 301: end 302: tags[ is_open_tag ? 0 : 1] 303: end 304: 305: def list_item_start(am, fragment) 306: case fragment.type 307: when ListBase::BULLET, ListBase::NUMBER, ListBase::UPPERALPHA, ListBase::LOWERALPHA 308: "\\item " 309: 310: when ListBase::LABELED 311: "\\item[" + convert_flow(am.flow(fragment.param)) + "] " 312: 313: when ListBase::NOTE 314: convert_flow(am.flow(fragment.param)) + " & " 315: else 316: raise "Invalid list type" 317: end 318: end 319: 320: def list_end_for(fragment_type) 321: case fragment_type 322: when ListBase::BULLET, ListBase::NUMBER, ListBase::UPPERALPHA, ListBase::LOWERALPHA, ListBase::LABELED 323: "" 324: when ListBase::NOTE 325: "\\\\\n" 326: else 327: raise "Invalid list type" 328: end 329: end 330: 331: end 332: 333: end 334:
Set up the standard mapping of attributes to LaTeX
# File markup/simple_markup/to_latex.rb, line 54 54: def init_tags 55: @attr_tags = [ 56: InlineTag.new(SM::Attribute.bitmap_for(:BOLD), l("\\textbf{"), l("}")), 57: InlineTag.new(SM::Attribute.bitmap_for(:TT), l("\\texttt{"), l("}")), 58: InlineTag.new(SM::Attribute.bitmap_for(:EM), l("\\emph{"), l("}")), 59: ] 60: end