source: extensions/gsdl-video/trunk/installed/cmdline/lib/ruby/1.8/rdoc/markup/simple_markup/to_latex.rb@ 18425

Last change on this file since 18425 was 18425, checked in by davidb, 15 years ago

Video extension to Greenstone

File size: 8.3 KB
Line 
1require 'rdoc/markup/simple_markup/fragments'
2require 'rdoc/markup/simple_markup/inline'
3
4require 'cgi'
5
6module SM
7
8 # Convert SimpleMarkup to basic LaTeX report format
9
10 class ToLaTeX
11
12 BS = "\020" # \
13 OB = "\021" # {
14 CB = "\022" # }
15 DL = "\023" # Dollar
16
17 BACKSLASH = "#{BS}symbol#{OB}92#{CB}"
18 HAT = "#{BS}symbol#{OB}94#{CB}"
19 BACKQUOTE = "#{BS}symbol#{OB}0#{CB}"
20 TILDE = "#{DL}#{BS}sim#{DL}"
21 LESSTHAN = "#{DL}<#{DL}"
22 GREATERTHAN = "#{DL}>#{DL}"
23
24 def self.l(str)
25 str.tr('\\', BS).tr('{', OB).tr('}', CB).tr('$', DL)
26 end
27
28 def l(arg)
29 SM::ToLaTeX.l(arg)
30 end
31
32 LIST_TYPE_TO_LATEX = {
33 ListBase::BULLET => [ l("\\begin{itemize}"), l("\\end{itemize}") ],
34 ListBase::NUMBER => [ l("\\begin{enumerate}"), l("\\end{enumerate}"), "\\arabic" ],
35 ListBase::UPPERALPHA => [ l("\\begin{enumerate}"), l("\\end{enumerate}"), "\\Alph" ],
36 ListBase::LOWERALPHA => [ l("\\begin{enumerate}"), l("\\end{enumerate}"), "\\alph" ],
37 ListBase::LABELED => [ l("\\begin{description}"), l("\\end{description}") ],
38 ListBase::NOTE => [
39 l("\\begin{tabularx}{\\linewidth}{@{} l X @{}}"),
40 l("\\end{tabularx}") ],
41 }
42
43 InlineTag = Struct.new(:bit, :on, :off)
44
45 def initialize
46 init_tags
47 @list_depth = 0
48 @prev_list_types = []
49 end
50
51 ##
52 # Set up the standard mapping of attributes to LaTeX
53 #
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
61
62 ##
63 # Escape a LaTeX string
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
333end
Note: See TracBrowser for help on using the repository browser.