Class SM::ToLaTeX
In: markup/simple_markup/to_latex.rb
Parent: Object

Convert SimpleMarkup to basic LaTeX report format

Methods

escape   init_tags   l   l   new  

Constants

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)

Public Class methods

[Source]

    # 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

[Source]

    # 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

Public Instance methods

Escape a LaTeX string

[Source]

     # 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

[Source]

    # 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

[Source]

    # File markup/simple_markup/to_latex.rb, line 28
28:     def l(arg)
29:       SM::ToLaTeX.l(arg)
30:     end

[Validate]