| Class | SM::ToLaTeX |
| In: |
markup/simple_markup/to_latex.rb
|
| Parent: | Object |
Convert SimpleMarkup to basic LaTeX report format
| 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
# File markup/simple_markup/to_latex.rb, line 139
139: def accept_blank_line(am, fragment)
140: # @res << "\n"
141: end
# File markup/simple_markup/to_latex.rb, line 143
143: def accept_heading(am, fragment)
144: @res << convert_heading(fragment.head_level, am.flow(fragment.txt))
145: end
# File markup/simple_markup/to_latex.rb, line 123
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
# File markup/simple_markup/to_latex.rb, line 130
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
# File markup/simple_markup/to_latex.rb, line 118
118: def accept_list_start(am, fragment)
119: @res << list_name(fragment.type, true) <<"\n"
120: @in_list_entry.push false
121: end
# File markup/simple_markup/to_latex.rb, line 101
101: def accept_paragraph(am, fragment)
102: @res << wrap(convert_flow(am.flow(fragment.txt)))
103: @res << "\n"
104: end
# File markup/simple_markup/to_latex.rb, line 112
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
# File markup/simple_markup/to_latex.rb, line 106
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
# File markup/simple_markup/to_latex.rb, line 204
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
# File markup/simple_markup/to_latex.rb, line 264
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
# File markup/simple_markup/to_latex.rb, line 251
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
some of these patterns are taken from SmartyPants...
# File markup/simple_markup/to_latex.rb, line 225
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\)/, '\ccopyright{}')
248:
249: end
# File markup/simple_markup/to_latex.rb, line 97
97: def end_accepting
98: @res.tr(BS, '\\').tr(OB, '{').tr(CB, '}').tr(DL, '$')
99: 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
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
# File markup/simple_markup/to_latex.rb, line 320
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
# File markup/simple_markup/to_latex.rb, line 305
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
# File markup/simple_markup/to_latex.rb, line 277
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
# File markup/simple_markup/to_latex.rb, line 193
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
# File markup/simple_markup/to_latex.rb, line 182
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
Here‘s the client side of the visitor pattern
# File markup/simple_markup/to_latex.rb, line 92
92: def start_accepting
93: @res = ""
94: @in_list_entry = []
95: end
This is a higher speed (if messier) version of wrap
# File markup/simple_markup/to_latex.rb, line 149
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