##// END OF EJS Templates
somme support for utf-8 in latex...
Matthias BUSSONNIER -
Show More
@@ -1,712 +1,712 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 """Convert IPython notebooks to other formats, such as ReST, and HTML.
2 """Convert IPython notebooks to other formats, such as ReST, and HTML.
3
3
4 Example:
4 Example:
5 ./nbconvert.py --format html file.ipynb
5 ./nbconvert.py --format html file.ipynb
6
6
7 Produces 'file.rst' and 'file.html', along with auto-generated figure files
7 Produces 'file.rst' and 'file.html', along with auto-generated figure files
8 called nb_figure_NN.png. To avoid the two-step process, ipynb -> rst -> html,
8 called nb_figure_NN.png. To avoid the two-step process, ipynb -> rst -> html,
9 use '--format quick-html' which will do ipynb -> html, but won't look as
9 use '--format quick-html' which will do ipynb -> html, but won't look as
10 pretty.
10 pretty.
11 """
11 """
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13 # Imports
13 # Imports
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 from __future__ import print_function
15 from __future__ import print_function
16
16
17 # Stdlib
17 # Stdlib
18 import codecs
18 import codecs
19 import logging
19 import logging
20 import os
20 import os
21 import pprint
21 import pprint
22 import re
22 import re
23 import subprocess
23 import subprocess
24 import sys
24 import sys
25
25
26 inkscape = 'inkscape'
26 inkscape = 'inkscape'
27 if sys.platform == 'darwin':
27 if sys.platform == 'darwin':
28 inkscape = '/Applications/Inkscape.app/Contents/Resources/bin/inkscape'
28 inkscape = '/Applications/Inkscape.app/Contents/Resources/bin/inkscape'
29 if not os.path.exists(inkscape):
29 if not os.path.exists(inkscape):
30 inkscape = None
30 inkscape = None
31
31
32 # From IPython
32 # From IPython
33 from IPython.external import argparse
33 from IPython.external import argparse
34 from IPython.nbformat import current as nbformat
34 from IPython.nbformat import current as nbformat
35 from IPython.utils.text import indent
35 from IPython.utils.text import indent
36 from decorators import DocInherit
36 from decorators import DocInherit
37
37
38 #-----------------------------------------------------------------------------
38 #-----------------------------------------------------------------------------
39 # Utility functions
39 # Utility functions
40 #-----------------------------------------------------------------------------
40 #-----------------------------------------------------------------------------
41
41
42 def remove_fake_files_url(cell):
42 def remove_fake_files_url(cell):
43 """Remove from the cell source the /files/ pseudo-path we use.
43 """Remove from the cell source the /files/ pseudo-path we use.
44 """
44 """
45 src = cell.source
45 src = cell.source
46 cell.source = src.replace('/files/', '')
46 cell.source = src.replace('/files/', '')
47
47
48
48
49 def remove_ansi(src):
49 def remove_ansi(src):
50 """Strip all ANSI color escape sequences from input string.
50 """Strip all ANSI color escape sequences from input string.
51
51
52 Parameters
52 Parameters
53 ----------
53 ----------
54 src : string
54 src : string
55
55
56 Returns
56 Returns
57 -------
57 -------
58 string
58 string
59 """
59 """
60 return re.sub(r'\033\[(0|\d;\d\d)m', '', src)
60 return re.sub(r'\033\[(0|\d;\d\d)m', '', src)
61
61
62
62
63 # Pandoc-dependent code
63 # Pandoc-dependent code
64 def markdown2latex(src):
64 def markdown2latex(src):
65 """Convert a markdown string to LaTeX via pandoc.
65 """Convert a markdown string to LaTeX via pandoc.
66
66
67 This function will raise an error if pandoc is not installed.
67 This function will raise an error if pandoc is not installed.
68
68
69 Any error messages generated by pandoc are printed to stderr.
69 Any error messages generated by pandoc are printed to stderr.
70
70
71 Parameters
71 Parameters
72 ----------
72 ----------
73 src : string
73 src : string
74 Input string, assumed to be valid markdown.
74 Input string, assumed to be valid markdown.
75
75
76 Returns
76 Returns
77 -------
77 -------
78 out : string
78 out : string
79 Output as returned by pandoc.
79 Output as returned by pandoc.
80 """
80 """
81 p = subprocess.Popen('pandoc -f markdown -t latex'.split(),
81 p = subprocess.Popen('pandoc -f markdown -t latex'.split(),
82 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
82 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
83 out, err = p.communicate(src)
83 out, err = p.communicate(src.encode('utf-8'))
84 if err:
84 if err:
85 print(err, file=sys.stderr)
85 print(err, file=sys.stderr)
86 #print('*'*20+'\n', out, '\n'+'*'*20) # dbg
86 #print('*'*20+'\n', out, '\n'+'*'*20) # dbg
87 return out
87 return unicode(out,'utf-8')
88
88
89
89
90 def rst_directive(directive, text=''):
90 def rst_directive(directive, text=''):
91 out = [directive, '']
91 out = [directive, '']
92 if text:
92 if text:
93 out.extend([indent(text), ''])
93 out.extend([indent(text), ''])
94 return out
94 return out
95
95
96 #-----------------------------------------------------------------------------
96 #-----------------------------------------------------------------------------
97 # Class declarations
97 # Class declarations
98 #-----------------------------------------------------------------------------
98 #-----------------------------------------------------------------------------
99
99
100 class ConversionException(Exception):
100 class ConversionException(Exception):
101 pass
101 pass
102
102
103
103
104 class Converter(object):
104 class Converter(object):
105 default_encoding = 'utf-8'
105 default_encoding = 'utf-8'
106 extension = str()
106 extension = str()
107 figures_counter = 0
107 figures_counter = 0
108 infile = str()
108 infile = str()
109 infile_dir = str()
109 infile_dir = str()
110 infile_root = str()
110 infile_root = str()
111 files_dir = str()
111 files_dir = str()
112 with_preamble = True
112 with_preamble = True
113 user_preamble = None
113 user_preamble = None
114 output = str()
114 output = str()
115 raw_as_verbatim = False
115 raw_as_verbatim = False
116
116
117 def __init__(self, infile):
117 def __init__(self, infile):
118 self.infile = infile
118 self.infile = infile
119 self.infile_dir = os.path.dirname(infile)
119 self.infile_dir = os.path.dirname(infile)
120 infile_root = os.path.splitext(infile)[0]
120 infile_root = os.path.splitext(infile)[0]
121 files_dir = infile_root + '_files'
121 files_dir = infile_root + '_files'
122 if not os.path.isdir(files_dir):
122 if not os.path.isdir(files_dir):
123 os.mkdir(files_dir)
123 os.mkdir(files_dir)
124 self.infile_root = infile_root
124 self.infile_root = infile_root
125 self.files_dir = files_dir
125 self.files_dir = files_dir
126
126
127 def dispatch(self, cell_type):
127 def dispatch(self, cell_type):
128 """return cell_type dependent render method, for example render_code
128 """return cell_type dependent render method, for example render_code
129 """
129 """
130 return getattr(self, 'render_' + cell_type, self.render_unknown)
130 return getattr(self, 'render_' + cell_type, self.render_unknown)
131
131
132 def convert(self):
132 def convert(self):
133 lines = []
133 lines = []
134 lines.extend(self.optional_header())
134 lines.extend(self.optional_header())
135 for worksheet in self.nb.worksheets:
135 for worksheet in self.nb.worksheets:
136 for cell in worksheet.cells:
136 for cell in worksheet.cells:
137 #print(cell.cell_type) # dbg
137 #print(cell.cell_type) # dbg
138 conv_fn = self.dispatch(cell.cell_type)
138 conv_fn = self.dispatch(cell.cell_type)
139 if cell.cell_type in ('markdown', 'raw'):
139 if cell.cell_type in ('markdown', 'raw'):
140 remove_fake_files_url(cell)
140 remove_fake_files_url(cell)
141 lines.extend(conv_fn(cell))
141 lines.extend(conv_fn(cell))
142 lines.append('')
142 lines.append(u'')
143 lines.extend(self.optional_footer())
143 lines.extend(self.optional_footer())
144 return '\n'.join(lines)
144 return u'\n'.join(lines)
145
145
146 def render(self):
146 def render(self):
147 "read, convert, and save self.infile"
147 "read, convert, and save self.infile"
148 self.read()
148 self.read()
149 self.output = self.convert()
149 self.output = self.convert()
150 return self.save()
150 return self.save()
151
151
152 def read(self):
152 def read(self):
153 "read and parse notebook into NotebookNode called self.nb"
153 "read and parse notebook into NotebookNode called self.nb"
154 with open(self.infile) as f:
154 with open(self.infile) as f:
155 self.nb = nbformat.read(f, 'json')
155 self.nb = nbformat.read(f, 'json')
156
156
157 def save(self, infile=None, encoding=None):
157 def save(self, infile=None, encoding=None):
158 "read and parse notebook into self.nb"
158 "read and parse notebook into self.nb"
159 if infile is None:
159 if infile is None:
160 outfile = os.path.basename(self.infile)
160 outfile = os.path.basename(self.infile)
161 outfile = os.path.splitext(outfile)[0] + '.' + self.extension
161 outfile = os.path.splitext(outfile)[0] + '.' + self.extension
162 if encoding is None:
162 if encoding is None:
163 encoding = self.default_encoding
163 encoding = self.default_encoding
164 with open(outfile, 'w') as f:
164 with open(outfile, 'w') as f:
165 f.write(self.output.encode(encoding))
165 f.write(self.output.encode(encoding))
166 return os.path.abspath(outfile)
166 return os.path.abspath(outfile)
167
167
168 def optional_header(self):
168 def optional_header(self):
169 return []
169 return []
170
170
171 def optional_footer(self):
171 def optional_footer(self):
172 return []
172 return []
173
173
174 def _new_figure(self, data, fmt):
174 def _new_figure(self, data, fmt):
175 """Create a new figure file in the given format.
175 """Create a new figure file in the given format.
176
176
177 Returns a path relative to the input file.
177 Returns a path relative to the input file.
178 """
178 """
179 figname = '%s_fig_%02i.%s' % (self.infile_root,
179 figname = '%s_fig_%02i.%s' % (self.infile_root,
180 self.figures_counter, fmt)
180 self.figures_counter, fmt)
181 self.figures_counter += 1
181 self.figures_counter += 1
182 fullname = os.path.join(self.files_dir, figname)
182 fullname = os.path.join(self.files_dir, figname)
183
183
184 # Binary files are base64-encoded, SVG is already XML
184 # Binary files are base64-encoded, SVG is already XML
185 if fmt in ('png', 'jpg', 'pdf'):
185 if fmt in ('png', 'jpg', 'pdf'):
186 data = data.decode('base64')
186 data = data.decode('base64')
187 fopen = lambda fname: open(fname, 'wb')
187 fopen = lambda fname: open(fname, 'wb')
188 else:
188 else:
189 fopen = lambda fname: codecs.open(fname, 'wb', self.default_encoding)
189 fopen = lambda fname: codecs.open(fname, 'wb', self.default_encoding)
190
190
191 with fopen(fullname) as f:
191 with fopen(fullname) as f:
192 f.write(data)
192 f.write(data)
193
193
194 return fullname
194 return fullname
195
195
196 def render_heading(self, cell):
196 def render_heading(self, cell):
197 """convert a heading cell
197 """convert a heading cell
198
198
199 Returns list."""
199 Returns list."""
200 raise NotImplementedError
200 raise NotImplementedError
201
201
202 def render_code(self, cell):
202 def render_code(self, cell):
203 """Convert a code cell
203 """Convert a code cell
204
204
205 Returns list."""
205 Returns list."""
206 raise NotImplementedError
206 raise NotImplementedError
207
207
208 def render_markdown(self, cell):
208 def render_markdown(self, cell):
209 """convert a markdown cell
209 """convert a markdown cell
210
210
211 Returns list."""
211 Returns list."""
212 raise NotImplementedError
212 raise NotImplementedError
213
213
214 def render_pyout(self, output):
214 def render_pyout(self, output):
215 """convert pyout part of a code cell
215 """convert pyout part of a code cell
216
216
217 Returns list."""
217 Returns list."""
218 raise NotImplementedError
218 raise NotImplementedError
219
219
220
220
221 def render_pyerr(self, output):
221 def render_pyerr(self, output):
222 """convert pyerr part of a code cell
222 """convert pyerr part of a code cell
223
223
224 Returns list."""
224 Returns list."""
225 raise NotImplementedError
225 raise NotImplementedError
226
226
227 def _img_lines(self, img_file):
227 def _img_lines(self, img_file):
228 """Return list of lines to include an image file."""
228 """Return list of lines to include an image file."""
229 # Note: subclasses may choose to implement format-specific _FMT_lines
229 # Note: subclasses may choose to implement format-specific _FMT_lines
230 # methods if they so choose (FMT in {png, svg, jpg, pdf}).
230 # methods if they so choose (FMT in {png, svg, jpg, pdf}).
231 raise NotImplementedError
231 raise NotImplementedError
232
232
233 def render_display_data(self, output):
233 def render_display_data(self, output):
234 """convert display data from the output of a code cell
234 """convert display data from the output of a code cell
235
235
236 Returns list.
236 Returns list.
237 """
237 """
238 lines = []
238 lines = []
239
239
240 for fmt in ['png', 'svg', 'jpg', 'pdf']:
240 for fmt in ['png', 'svg', 'jpg', 'pdf']:
241 if fmt in output:
241 if fmt in output:
242 img_file = self._new_figure(output[fmt], fmt)
242 img_file = self._new_figure(output[fmt], fmt)
243 # Subclasses can have format-specific render functions (e.g.,
243 # Subclasses can have format-specific render functions (e.g.,
244 # latex has to auto-convert all SVG to PDF first).
244 # latex has to auto-convert all SVG to PDF first).
245 lines_fun = getattr(self, '_%s_lines' % fmt, None)
245 lines_fun = getattr(self, '_%s_lines' % fmt, None)
246 if not lines_fun:
246 if not lines_fun:
247 lines_fun = self._img_lines
247 lines_fun = self._img_lines
248 lines.extend(lines_fun(img_file))
248 lines.extend(lines_fun(img_file))
249
249
250 return lines
250 return lines
251
251
252 def render_stream(self, cell):
252 def render_stream(self, cell):
253 """convert stream part of a code cell
253 """convert stream part of a code cell
254
254
255 Returns list."""
255 Returns list."""
256 raise NotImplementedError
256 raise NotImplementedError
257
257
258 def render_raw(self, cell):
258 def render_raw(self, cell):
259 """convert a cell with raw text
259 """convert a cell with raw text
260
260
261 Returns list."""
261 Returns list."""
262 raise NotImplementedError
262 raise NotImplementedError
263
263
264 def render_unknown(self, cell):
264 def render_unknown(self, cell):
265 """Render cells of unkown type
265 """Render cells of unkown type
266
266
267 Returns list."""
267 Returns list."""
268 data = pprint.pformat(cell)
268 data = pprint.pformat(cell)
269 logging.warning('Unknown cell:\n%s' % data)
269 logging.warning('Unknown cell:\n%s' % data)
270 return self._unknown_lines(data)
270 return self._unknown_lines(data)
271
271
272 def _unknown_lines(self, data):
272 def _unknown_lines(self, data):
273 """Return list of lines for an unknown cell.
273 """Return list of lines for an unknown cell.
274
274
275 Parameters
275 Parameters
276 ----------
276 ----------
277 data : str
277 data : str
278 The content of the unknown data as a single string.
278 The content of the unknown data as a single string.
279 """
279 """
280 raise NotImplementedError
280 raise NotImplementedError
281
281
282
282
283 class ConverterRST(Converter):
283 class ConverterRST(Converter):
284 extension = 'rst'
284 extension = 'rst'
285 heading_level = {1: '=', 2: '-', 3: '`', 4: '\'', 5: '.', 6: '~'}
285 heading_level = {1: '=', 2: '-', 3: '`', 4: '\'', 5: '.', 6: '~'}
286
286
287 @DocInherit
287 @DocInherit
288 def render_heading(self, cell):
288 def render_heading(self, cell):
289 marker = self.heading_level[cell.level]
289 marker = self.heading_level[cell.level]
290 return ['{0}\n{1}\n'.format(cell.source, marker * len(cell.source))]
290 return ['{0}\n{1}\n'.format(cell.source, marker * len(cell.source))]
291
291
292 @DocInherit
292 @DocInherit
293 def render_code(self, cell):
293 def render_code(self, cell):
294 if not cell.input:
294 if not cell.input:
295 return []
295 return []
296
296
297 lines = ['In[%s]:' % cell.prompt_number, '']
297 lines = ['In[%s]:' % cell.prompt_number, '']
298 lines.extend(rst_directive('.. code:: python', cell.input))
298 lines.extend(rst_directive('.. code:: python', cell.input))
299
299
300 for output in cell.outputs:
300 for output in cell.outputs:
301 conv_fn = self.dispatch(output.output_type)
301 conv_fn = self.dispatch(output.output_type)
302 lines.extend(conv_fn(output))
302 lines.extend(conv_fn(output))
303
303
304 return lines
304 return lines
305
305
306 @DocInherit
306 @DocInherit
307 def render_markdown(self, cell):
307 def render_markdown(self, cell):
308 return [cell.source]
308 return [cell.source]
309
309
310 @DocInherit
310 @DocInherit
311 def render_raw(self, cell):
311 def render_raw(self, cell):
312 if self.raw_as_verbatim:
312 if self.raw_as_verbatim:
313 return ['::', '', indent(cell.source), '']
313 return ['::', '', indent(cell.source), '']
314 else:
314 else:
315 return [cell.source]
315 return [cell.source]
316
316
317 @DocInherit
317 @DocInherit
318 def render_pyout(self, output):
318 def render_pyout(self, output):
319 lines = ['Out[%s]:' % output.prompt_number, '']
319 lines = ['Out[%s]:' % output.prompt_number, '']
320
320
321 # output is a dictionary like object with type as a key
321 # output is a dictionary like object with type as a key
322 if 'latex' in output:
322 if 'latex' in output:
323 lines.extend(rst_directive('.. math::', output.latex))
323 lines.extend(rst_directive('.. math::', output.latex))
324
324
325 if 'text' in output:
325 if 'text' in output:
326 lines.extend(rst_directive('.. parsed-literal::', output.text))
326 lines.extend(rst_directive('.. parsed-literal::', output.text))
327
327
328 return lines
328 return lines
329
329
330 @DocInherit
330 @DocInherit
331 def render_pyerr(self, output):
331 def render_pyerr(self, output):
332 # Note: a traceback is a *list* of frames.
332 # Note: a traceback is a *list* of frames.
333 return ['::', '', indent(remove_ansi('\n'.join(output.traceback))), '']
333 return ['::', '', indent(remove_ansi('\n'.join(output.traceback))), '']
334
334
335 @DocInherit
335 @DocInherit
336 def _img_lines(self, img_file):
336 def _img_lines(self, img_file):
337 return ['.. image:: %s' % img_file, '']
337 return ['.. image:: %s' % img_file, '']
338
338
339 @DocInherit
339 @DocInherit
340 def render_stream(self, output):
340 def render_stream(self, output):
341 lines = []
341 lines = []
342
342
343 if 'text' in output:
343 if 'text' in output:
344 lines.extend(rst_directive('.. parsed-literal::', output.text))
344 lines.extend(rst_directive('.. parsed-literal::', output.text))
345
345
346 return lines
346 return lines
347
347
348 @DocInherit
348 @DocInherit
349 def _unknown_lines(self, data):
349 def _unknown_lines(self, data):
350 return rst_directive('.. warning:: Unknown cell') + [data]
350 return rst_directive('.. warning:: Unknown cell') + [data]
351
351
352
352
353 class ConverterQuickHTML(Converter):
353 class ConverterQuickHTML(Converter):
354 extension = 'html'
354 extension = 'html'
355
355
356 def in_tag(self, tag, src):
356 def in_tag(self, tag, src):
357 """Return a list of elements bracketed by the given tag"""
357 """Return a list of elements bracketed by the given tag"""
358 return ['<%s>' % tag, src, '</%s>' % tag]
358 return ['<%s>' % tag, src, '</%s>' % tag]
359
359
360 def optional_header(self):
360 def optional_header(self):
361 # XXX: inject the IPython standard CSS into here
361 # XXX: inject the IPython standard CSS into here
362 s = """<html>
362 s = """<html>
363 <head>
363 <head>
364 </head>
364 </head>
365
365
366 <body>
366 <body>
367 """
367 """
368 return s.splitlines()
368 return s.splitlines()
369
369
370 def optional_footer(self):
370 def optional_footer(self):
371 s = """</body>
371 s = """</body>
372 </html>
372 </html>
373 """
373 """
374 return s.splitlines()
374 return s.splitlines()
375
375
376 @DocInherit
376 @DocInherit
377 def render_heading(self, cell):
377 def render_heading(self, cell):
378 marker = cell.level
378 marker = cell.level
379 return ['<h{1}>\n {0}\n</h{1}>'.format(cell.source, marker)]
379 return ['<h{1}>\n {0}\n</h{1}>'.format(cell.source, marker)]
380
380
381 @DocInherit
381 @DocInherit
382 def render_code(self, cell):
382 def render_code(self, cell):
383 if not cell.input:
383 if not cell.input:
384 return []
384 return []
385
385
386 lines = ['<table>']
386 lines = ['<table>']
387 lines.append('<tr><td><tt>In [<b>%s</b>]:</tt></td><td><tt>' % cell.prompt_number)
387 lines.append('<tr><td><tt>In [<b>%s</b>]:</tt></td><td><tt>' % cell.prompt_number)
388 lines.append("<br>\n".join(cell.input.splitlines()))
388 lines.append("<br>\n".join(cell.input.splitlines()))
389 lines.append('</tt></td></tr>')
389 lines.append('</tt></td></tr>')
390
390
391 for output in cell.outputs:
391 for output in cell.outputs:
392 lines.append('<tr><td></td><td>')
392 lines.append('<tr><td></td><td>')
393 conv_fn = self.dispatch(output.output_type)
393 conv_fn = self.dispatch(output.output_type)
394 lines.extend(conv_fn(output))
394 lines.extend(conv_fn(output))
395 lines.append('</td></tr>')
395 lines.append('</td></tr>')
396
396
397 lines.append('</table>')
397 lines.append('</table>')
398 return lines
398 return lines
399
399
400 @DocInherit
400 @DocInherit
401 def render_markdown(self, cell):
401 def render_markdown(self, cell):
402 return self.in_tag('pre', cell.source)
402 return self.in_tag('pre', cell.source)
403
403
404 @DocInherit
404 @DocInherit
405 def render_raw(self, cell):
405 def render_raw(self, cell):
406 if self.raw_as_verbatim:
406 if self.raw_as_verbatim:
407 return self.in_tag('pre', cell.source)
407 return self.in_tag('pre', cell.source)
408 else:
408 else:
409 return [cell.source]
409 return [cell.source]
410
410
411 @DocInherit
411 @DocInherit
412 def render_pyout(self, output):
412 def render_pyout(self, output):
413 lines = ['<tr><td><tt>Out[<b>%s</b>]:</tt></td></tr>' %
413 lines = ['<tr><td><tt>Out[<b>%s</b>]:</tt></td></tr>' %
414 output.prompt_number, '<td>']
414 output.prompt_number, '<td>']
415
415
416 # output is a dictionary like object with type as a key
416 # output is a dictionary like object with type as a key
417 for out_type in ('text', 'latex'):
417 for out_type in ('text', 'latex'):
418 if out_type in output:
418 if out_type in output:
419 lines.extend(self.in_tag('pre', indent(output[out_type])))
419 lines.extend(self.in_tag('pre', indent(output[out_type])))
420
420
421 return lines
421 return lines
422
422
423 @DocInherit
423 @DocInherit
424 def render_pyerr(self, output):
424 def render_pyerr(self, output):
425 # Note: a traceback is a *list* of frames.
425 # Note: a traceback is a *list* of frames.
426 return self.in_tag('pre', remove_ansi('\n'.join(output.traceback)))
426 return self.in_tag('pre', remove_ansi('\n'.join(output.traceback)))
427
427
428 @DocInherit
428 @DocInherit
429 def _img_lines(self, img_file):
429 def _img_lines(self, img_file):
430 return ['<img src="%s">' % img_file, '']
430 return ['<img src="%s">' % img_file, '']
431
431
432 @DocInherit
432 @DocInherit
433 def render_stream(self, output):
433 def render_stream(self, output):
434 lines = []
434 lines = []
435
435
436 if 'text' in output:
436 if 'text' in output:
437 lines.append(output.text)
437 lines.append(output.text)
438
438
439 return lines
439 return lines
440
440
441 @DocInherit
441 @DocInherit
442 def _unknown_lines(self, data):
442 def _unknown_lines(self, data):
443 return ['<h2>Warning:: Unknown cell</h2>'] + self.in_tag('pre', data)
443 return ['<h2>Warning:: Unknown cell</h2>'] + self.in_tag('pre', data)
444
444
445
445
446 class ConverterLaTeX(Converter):
446 class ConverterLaTeX(Converter):
447 """Converts a notebook to a .tex file suitable for pdflatex.
447 """Converts a notebook to a .tex file suitable for pdflatex.
448
448
449 Note: this converter *needs*:
449 Note: this converter *needs*:
450
450
451 - `pandoc`: for all conversion of markdown cells. If your notebook only
451 - `pandoc`: for all conversion of markdown cells. If your notebook only
452 has Raw cells, pandoc will not be needed.
452 has Raw cells, pandoc will not be needed.
453
453
454 - `inkscape`: if your notebook has SVG figures. These need to be
454 - `inkscape`: if your notebook has SVG figures. These need to be
455 converted to PDF before inclusion in the TeX file, as LaTeX doesn't
455 converted to PDF before inclusion in the TeX file, as LaTeX doesn't
456 understand SVG natively.
456 understand SVG natively.
457
457
458 You will in general obtain much better final PDF results if you configure
458 You will in general obtain much better final PDF results if you configure
459 the matplotlib backend to create SVG output with
459 the matplotlib backend to create SVG output with
460
460
461 %config InlineBackend.figure_format = 'svg'
461 %config InlineBackend.figure_format = 'svg'
462
462
463 (or set the equivalent flag at startup or in your configuration profile).
463 (or set the equivalent flag at startup or in your configuration profile).
464 """
464 """
465 extension = 'tex'
465 extension = 'tex'
466 documentclass = 'article'
466 documentclass = 'article'
467 documentclass_options = '11pt,english'
467 documentclass_options = '11pt,english'
468 heading_map = {1: r'\section',
468 heading_map = {1: r'\section',
469 2: r'\subsection',
469 2: r'\subsection',
470 3: r'\subsubsection',
470 3: r'\subsubsection',
471 4: r'\paragraph',
471 4: r'\paragraph',
472 5: r'\subparagraph',
472 5: r'\subparagraph',
473 6: r'\subparagraph'}
473 6: r'\subparagraph'}
474
474
475 def in_env(self, environment, lines):
475 def in_env(self, environment, lines):
476 """Return list of environment lines for input lines
476 """Return list of environment lines for input lines
477
477
478 Parameters
478 Parameters
479 ----------
479 ----------
480 env : string
480 env : string
481 Name of the environment to bracket with begin/end.
481 Name of the environment to bracket with begin/end.
482
482
483 lines: """
483 lines: """
484 out = [r'\begin{%s}' % environment]
484 out = [ur'\begin{%s}' % environment]
485 if isinstance(lines, basestring):
485 if isinstance(lines, basestring):
486 out.append(lines)
486 out.append(lines)
487 else: # list
487 else: # list
488 out.extend(lines)
488 out.extend(lines)
489 out.append(r'\end{%s}' % environment)
489 out.append(ur'\end{%s}' % environment)
490 return out
490 return out
491
491
492 def convert(self):
492 def convert(self):
493 # The main body is done by the logic in the parent class, and that's
493 # The main body is done by the logic in the parent class, and that's
494 # all we need if preamble support has been turned off.
494 # all we need if preamble support has been turned off.
495 body = super(ConverterLaTeX, self).convert()
495 body = super(ConverterLaTeX, self).convert()
496 if not self.with_preamble:
496 if not self.with_preamble:
497 return body
497 return body
498 # But if preamble is on, then we need to construct a proper, standalone
498 # But if preamble is on, then we need to construct a proper, standalone
499 # tex file.
499 # tex file.
500
500
501 # Tag the document at the top and set latex class
501 # Tag the document at the top and set latex class
502 final = [ r'%% This file was auto-generated by IPython, do NOT edit',
502 final = [ r'%% This file was auto-generated by IPython, do NOT edit',
503 r'%% Conversion from the original notebook file:',
503 r'%% Conversion from the original notebook file:',
504 r'%% {0}'.format(self.infile),
504 r'%% {0}'.format(self.infile),
505 r'%%',
505 r'%%',
506 r'\documentclass[%s]{%s}' % (self.documentclass_options,
506 r'\documentclass[%s]{%s}' % (self.documentclass_options,
507 self.documentclass),
507 self.documentclass),
508 '',
508 '',
509 ]
509 ]
510 # Load our own preamble, which is stored next to the main file. We
510 # Load our own preamble, which is stored next to the main file. We
511 # need to be careful in case the script entry point is a symlink
511 # need to be careful in case the script entry point is a symlink
512 myfile = __file__ if not os.path.islink(__file__) else \
512 myfile = __file__ if not os.path.islink(__file__) else \
513 os.readlink(__file__)
513 os.readlink(__file__)
514 with open(os.path.join(os.path.dirname(myfile), 'preamble.tex')) as f:
514 with open(os.path.join(os.path.dirname(myfile), 'preamble.tex')) as f:
515 final.append(f.read())
515 final.append(f.read())
516
516
517 # Load any additional user-supplied preamble
517 # Load any additional user-supplied preamble
518 if self.user_preamble:
518 if self.user_preamble:
519 final.extend(['', '%% Adding user preamble from file:',
519 final.extend(['', '%% Adding user preamble from file:',
520 '%% {0}'.format(self.user_preamble), ''])
520 '%% {0}'.format(self.user_preamble), ''])
521 with open(self.user_preamble) as f:
521 with open(self.user_preamble) as f:
522 final.append(f.read())
522 final.append(f.read())
523
523
524 # Include document body
524 # Include document body
525 final.extend([ r'\begin{document}', '',
525 final.extend([ r'\begin{document}', '',
526 body,
526 body,
527 r'\end{document}', ''])
527 r'\end{document}', ''])
528 # Retun value must be a string
528 # Retun value must be a string
529 return '\n'.join(final)
529 return '\n'.join(final)
530
530
531 @DocInherit
531 @DocInherit
532 def render_heading(self, cell):
532 def render_heading(self, cell):
533 marker = self.heading_map[cell.level]
533 marker = self.heading_map[cell.level]
534 return ['%s{%s}' % (marker, cell.source) ]
534 return ['%s{%s}' % (marker, cell.source) ]
535
535
536 @DocInherit
536 @DocInherit
537 def render_code(self, cell):
537 def render_code(self, cell):
538 if not cell.input:
538 if not cell.input:
539 return []
539 return []
540
540
541 # Cell codes first carry input code, we use lstlisting for that
541 # Cell codes first carry input code, we use lstlisting for that
542 lines = [r'\begin{codecell}']
542 lines = [ur'\begin{codecell}']
543
543
544 lines.extend(self.in_env('codeinput',
544 lines.extend(self.in_env('codeinput',
545 self.in_env('lstlisting', cell.input)))
545 self.in_env('lstlisting', cell.input)))
546
546
547 outlines = []
547 outlines = []
548 for output in cell.outputs:
548 for output in cell.outputs:
549 conv_fn = self.dispatch(output.output_type)
549 conv_fn = self.dispatch(output.output_type)
550 outlines.extend(conv_fn(output))
550 outlines.extend(conv_fn(output))
551
551
552 # And then output of many possible types; use a frame for all of it.
552 # And then output of many possible types; use a frame for all of it.
553 if outlines:
553 if outlines:
554 lines.extend(self.in_env('codeoutput', outlines))
554 lines.extend(self.in_env('codeoutput', outlines))
555
555
556 lines.append(r'\end{codecell}')
556 lines.append(ur'\end{codecell}')
557
557
558 return lines
558 return lines
559
559
560
560
561 @DocInherit
561 @DocInherit
562 def _img_lines(self, img_file):
562 def _img_lines(self, img_file):
563 return self.in_env('center',
563 return self.in_env('center',
564 [r'\includegraphics[width=3in]{%s}' % img_file, r'\par'])
564 [r'\includegraphics[width=3in]{%s}' % img_file, r'\par'])
565
565
566 def _svg_lines(self, img_file):
566 def _svg_lines(self, img_file):
567 base_file = os.path.splitext(img_file)[0]
567 base_file = os.path.splitext(img_file)[0]
568 pdf_file = base_file + '.pdf'
568 pdf_file = base_file + '.pdf'
569 subprocess.check_call([ inkscape, '--export-pdf=%s' % pdf_file,
569 subprocess.check_call([ inkscape, '--export-pdf=%s' % pdf_file,
570 img_file])
570 img_file])
571 return self._img_lines(pdf_file)
571 return self._img_lines(pdf_file)
572
572
573 @DocInherit
573 @DocInherit
574 def render_stream(self, output):
574 def render_stream(self, output):
575 lines = []
575 lines = []
576
576
577 if 'text' in output:
577 if 'text' in output:
578 lines.extend(self.in_env('verbatim', output.text.strip()))
578 lines.extend(self.in_env('verbatim', output.text.strip()))
579
579
580 return lines
580 return lines
581
581
582 @DocInherit
582 @DocInherit
583 def render_markdown(self, cell):
583 def render_markdown(self, cell):
584 return [markdown2latex(cell.source)]
584 return [markdown2latex(cell.source)]
585
585
586 @DocInherit
586 @DocInherit
587 def render_pyout(self, output):
587 def render_pyout(self, output):
588 lines = []
588 lines = []
589
589
590 # output is a dictionary like object with type as a key
590 # output is a dictionary like object with type as a key
591 if 'latex' in output:
591 if 'latex' in output:
592 lines.extend(output.latex)
592 lines.extend(output.latex)
593
593
594 if 'text' in output:
594 if 'text' in output:
595 lines.extend(self.in_env('verbatim', output.text))
595 lines.extend(self.in_env('verbatim', output.text))
596
596
597 return lines
597 return lines
598
598
599 @DocInherit
599 @DocInherit
600 def render_pyerr(self, output):
600 def render_pyerr(self, output):
601 # Note: a traceback is a *list* of frames.
601 # Note: a traceback is a *list* of frames.
602 return self.in_env('traceback',
602 return self.in_env('traceback',
603 self.in_env('verbatim',
603 self.in_env('verbatim',
604 remove_ansi('\n'.join(output.traceback))))
604 remove_ansi('\n'.join(output.traceback))))
605
605
606 @DocInherit
606 @DocInherit
607 def render_raw(self, cell):
607 def render_raw(self, cell):
608 if self.raw_as_verbatim:
608 if self.raw_as_verbatim:
609 return self.in_env('verbatim', cell.source)
609 return self.in_env('verbatim', cell.source)
610 else:
610 else:
611 return [cell.source]
611 return [cell.source]
612
612
613 @DocInherit
613 @DocInherit
614 def _unknown_lines(self, data):
614 def _unknown_lines(self, data):
615 return [r'{\vspace{5mm}\bf WARNING:: unknown cell:}'] + \
615 return [r'{\vspace{5mm}\bf WARNING:: unknown cell:}'] + \
616 self.in_env('verbatim', data)
616 self.in_env('verbatim', data)
617
617
618 #-----------------------------------------------------------------------------
618 #-----------------------------------------------------------------------------
619 # Standalone conversion functions
619 # Standalone conversion functions
620 #-----------------------------------------------------------------------------
620 #-----------------------------------------------------------------------------
621
621
622 def rst2simplehtml(infile):
622 def rst2simplehtml(infile):
623 """Convert a rst file to simplified html suitable for blogger.
623 """Convert a rst file to simplified html suitable for blogger.
624
624
625 This just runs rst2html with certain parameters to produce really simple
625 This just runs rst2html with certain parameters to produce really simple
626 html and strips the document header, so the resulting file can be easily
626 html and strips the document header, so the resulting file can be easily
627 pasted into a blogger edit window.
627 pasted into a blogger edit window.
628 """
628 """
629
629
630 # This is the template for the rst2html call that produces the cleanest,
630 # This is the template for the rst2html call that produces the cleanest,
631 # simplest html I could find. This should help in making it easier to
631 # simplest html I could find. This should help in making it easier to
632 # paste into the blogspot html window, though I'm still having problems
632 # paste into the blogspot html window, though I'm still having problems
633 # with linebreaks there...
633 # with linebreaks there...
634 cmd_template = ("rst2html --link-stylesheet --no-xml-declaration "
634 cmd_template = ("rst2html --link-stylesheet --no-xml-declaration "
635 "--no-generator --no-datestamp --no-source-link "
635 "--no-generator --no-datestamp --no-source-link "
636 "--no-toc-backlinks --no-section-numbering "
636 "--no-toc-backlinks --no-section-numbering "
637 "--strip-comments ")
637 "--strip-comments ")
638
638
639 cmd = "%s %s" % (cmd_template, infile)
639 cmd = "%s %s" % (cmd_template, infile)
640 proc = subprocess.Popen(cmd,
640 proc = subprocess.Popen(cmd,
641 stdout=subprocess.PIPE,
641 stdout=subprocess.PIPE,
642 stderr=subprocess.PIPE,
642 stderr=subprocess.PIPE,
643 shell=True)
643 shell=True)
644 html, stderr = proc.communicate()
644 html, stderr = proc.communicate()
645 if stderr:
645 if stderr:
646 raise IOError(stderr)
646 raise IOError(stderr)
647
647
648 # Make an iterator so breaking out holds state. Our implementation of
648 # Make an iterator so breaking out holds state. Our implementation of
649 # searching for the html body below is basically a trivial little state
649 # searching for the html body below is basically a trivial little state
650 # machine, so we need this.
650 # machine, so we need this.
651 walker = iter(html.splitlines())
651 walker = iter(html.splitlines())
652
652
653 # Find start of main text, break out to then print until we find end /div.
653 # Find start of main text, break out to then print until we find end /div.
654 # This may only work if there's a real title defined so we get a 'div class'
654 # This may only work if there's a real title defined so we get a 'div class'
655 # tag, I haven't really tried.
655 # tag, I haven't really tried.
656 for line in walker:
656 for line in walker:
657 if line.startswith('<body>'):
657 if line.startswith('<body>'):
658 break
658 break
659
659
660 newfname = os.path.splitext(infile)[0] + '.html'
660 newfname = os.path.splitext(infile)[0] + '.html'
661 with open(newfname, 'w') as f:
661 with open(newfname, 'w') as f:
662 for line in walker:
662 for line in walker:
663 if line.startswith('</body>'):
663 if line.startswith('</body>'):
664 break
664 break
665 f.write(line)
665 f.write(line)
666 f.write('\n')
666 f.write('\n')
667
667
668 return newfname
668 return newfname
669
669
670 known_formats = "rst (default), html, quick-html, latex"
670 known_formats = "rst (default), html, quick-html, latex"
671
671
672 def main(infile, format='rst'):
672 def main(infile, format='rst'):
673 """Convert a notebook to html in one step"""
673 """Convert a notebook to html in one step"""
674 # XXX: this is just quick and dirty for now. When adding a new format,
674 # XXX: this is just quick and dirty for now. When adding a new format,
675 # make sure to add it to the `known_formats` string above, which gets
675 # make sure to add it to the `known_formats` string above, which gets
676 # printed in in the catch-all else, as well as in the help
676 # printed in in the catch-all else, as well as in the help
677 if format == 'rst':
677 if format == 'rst':
678 converter = ConverterRST(infile)
678 converter = ConverterRST(infile)
679 converter.render()
679 converter.render()
680 elif format == 'html':
680 elif format == 'html':
681 #Currently, conversion to html is a 2 step process, nb->rst->html
681 #Currently, conversion to html is a 2 step process, nb->rst->html
682 converter = ConverterRST(infile)
682 converter = ConverterRST(infile)
683 rstfname = converter.render()
683 rstfname = converter.render()
684 rst2simplehtml(rstfname)
684 rst2simplehtml(rstfname)
685 elif format == 'quick-html':
685 elif format == 'quick-html':
686 converter = ConverterQuickHTML(infile)
686 converter = ConverterQuickHTML(infile)
687 rstfname = converter.render()
687 rstfname = converter.render()
688 elif format == 'latex':
688 elif format == 'latex':
689 converter = ConverterLaTeX(infile)
689 converter = ConverterLaTeX(infile)
690 latexfname = converter.render()
690 latexfname = converter.render()
691 else:
691 else:
692 raise SystemExit("Unknown format '%s', " % format +
692 raise SystemExit("Unknown format '%s', " % format +
693 "known formats are: " + known_formats)
693 "known formats are: " + known_formats)
694
694
695 #-----------------------------------------------------------------------------
695 #-----------------------------------------------------------------------------
696 # Script main
696 # Script main
697 #-----------------------------------------------------------------------------
697 #-----------------------------------------------------------------------------
698
698
699 if __name__ == '__main__':
699 if __name__ == '__main__':
700 parser = argparse.ArgumentParser(description=__doc__,
700 parser = argparse.ArgumentParser(description=__doc__,
701 formatter_class=argparse.RawTextHelpFormatter)
701 formatter_class=argparse.RawTextHelpFormatter)
702 # TODO: consider passing file like object around, rather than filenames
702 # TODO: consider passing file like object around, rather than filenames
703 # would allow us to process stdin, or even http streams
703 # would allow us to process stdin, or even http streams
704 #parser.add_argument('infile', nargs='?', type=argparse.FileType('r'), default=sys.stdin)
704 #parser.add_argument('infile', nargs='?', type=argparse.FileType('r'), default=sys.stdin)
705
705
706 #Require a filename as a positional argument
706 #Require a filename as a positional argument
707 parser.add_argument('infile', nargs=1)
707 parser.add_argument('infile', nargs=1)
708 parser.add_argument('-f', '--format', default='rst',
708 parser.add_argument('-f', '--format', default='rst',
709 help='Output format. Supported formats: \n' +
709 help='Output format. Supported formats: \n' +
710 known_formats)
710 known_formats)
711 args = parser.parse_args()
711 args = parser.parse_args()
712 main(infile=args.infile[0], format=args.format)
712 main(infile=args.infile[0], format=args.format)
@@ -1,94 +1,98 b''
1 %% This is the automatic preamble used by IPython. Note that it does *not*
1 %% This is the automatic preamble used by IPython. Note that it does *not*
2 %% include a documentclass declaration, that is added at runtime to the overall
2 %% include a documentclass declaration, that is added at runtime to the overall
3 %% document.
3 %% document.
4
4
5 \usepackage{amsmath}
5 \usepackage{amsmath}
6 \usepackage{amssymb}
6 \usepackage{amssymb}
7 \usepackage{graphicx}
7 \usepackage{graphicx}
8 \usepackage{ucs}
9 \usepackage[utf8x]{inputenc}
8
10
9 % needed for markdown enumerations to work
11 % needed for markdown enumerations to work
10 \usepackage{enumerate}
12 \usepackage{enumerate}
11
13
12 % Slightly bigger margins than the latex defaults
14 % Slightly bigger margins than the latex defaults
13 \usepackage{geometry}
15 \usepackage{geometry}
14 \geometry{verbose,tmargin=3cm,bmargin=3cm,lmargin=2.5cm,rmargin=2.5cm}
16 \geometry{verbose,tmargin=3cm,bmargin=3cm,lmargin=2.5cm,rmargin=2.5cm}
15
17
16 % Define a few colors for use in code, links and cell shading
18 % Define a few colors for use in code, links and cell shading
17 \usepackage{color}
19 \usepackage{color}
18 \definecolor{orange}{cmyk}{0,0.4,0.8,0.2}
20 \definecolor{orange}{cmyk}{0,0.4,0.8,0.2}
19 \definecolor{darkorange}{rgb}{.71,0.21,0.01}
21 \definecolor{darkorange}{rgb}{.71,0.21,0.01}
20 \definecolor{darkgreen}{rgb}{.12,.54,.11}
22 \definecolor{darkgreen}{rgb}{.12,.54,.11}
21 \definecolor{myteal}{rgb}{.26, .44, .56}
23 \definecolor{myteal}{rgb}{.26, .44, .56}
22 \definecolor{gray}{gray}{0.45}
24 \definecolor{gray}{gray}{0.45}
23 \definecolor{lightgray}{gray}{.95}
25 \definecolor{lightgray}{gray}{.95}
24 \definecolor{mediumgray}{gray}{.8}
26 \definecolor{mediumgray}{gray}{.8}
25 \definecolor{inputbackground}{rgb}{.95, .95, .85}
27 \definecolor{inputbackground}{rgb}{.95, .95, .85}
26 \definecolor{outputbackground}{rgb}{.95, .95, .95}
28 \definecolor{outputbackground}{rgb}{.95, .95, .95}
27 \definecolor{traceback}{rgb}{1, .95, .95}
29 \definecolor{traceback}{rgb}{1, .95, .95}
28
30
29 % Framed environments for code cells (inputs, outputs, errors, ...). The
31 % Framed environments for code cells (inputs, outputs, errors, ...). The
30 % various uses of \unskip (or not) at the end were fine-tuned by hand, so don't
32 % various uses of \unskip (or not) at the end were fine-tuned by hand, so don't
31 % randomly change them unless you're sure of the effect it will have.
33 % randomly change them unless you're sure of the effect it will have.
32 \usepackage{framed}
34 \usepackage{framed}
33
35
34 % remove extraneous vertical space in boxes
36 % remove extraneous vertical space in boxes
35 \setlength\fboxsep{0pt}
37 \setlength\fboxsep{0pt}
36
38
37 % codecell is the whole input+output set of blocks that a Code cell can
39 % codecell is the whole input+output set of blocks that a Code cell can
38 % generate.
40 % generate.
39 \newenvironment{codecell}{%
41 \newenvironment{codecell}{%
40 \def\FrameCommand{\color{mediumgray} \vrule width 1pt \hspace{5pt}}%
42 \def\FrameCommand{\color{mediumgray} \vrule width 1pt \hspace{5pt}}%
41 \MakeFramed{\vspace{-0.5em}\FrameRestore}}
43 \MakeFramed{\vspace{-0.5em}\FrameRestore}}
42 {\unskip\endMakeFramed}
44 {\unskip\endMakeFramed}
43
45
44 \newenvironment{codeinput}{%
46 \newenvironment{codeinput}{%
45 \def\FrameCommand{\colorbox{inputbackground}}%
47 \def\FrameCommand{\colorbox{inputbackground}}%
46 \MakeFramed{\advance\hsize-\width \FrameRestore}}
48 \MakeFramed{\advance\hsize-\width \FrameRestore}}
47 {\unskip\endMakeFramed}
49 {\unskip\endMakeFramed}
48
50
49 \newenvironment{codeoutput}{%
51 \newenvironment{codeoutput}{%
50 \def\FrameCommand{\colorbox{outputbackground}}%
52 \def\FrameCommand{\colorbox{outputbackground}}%
51 \vspace{-1.4em}
53 \vspace{-1.4em}
52 \MakeFramed{\advance\hsize-\width \FrameRestore}}
54 \MakeFramed{\advance\hsize-\width \FrameRestore}}
53 {\unskip\medskip\endMakeFramed}
55 {\unskip\medskip\endMakeFramed}
54
56
55 \newenvironment{traceback}{%
57 \newenvironment{traceback}{%
56 \def\FrameCommand{\colorbox{traceback}}%
58 \def\FrameCommand{\colorbox{traceback}}%
57 \MakeFramed{\advance\hsize-\width \FrameRestore}}
59 \MakeFramed{\advance\hsize-\width \FrameRestore}}
58 {\endMakeFramed}
60 {\endMakeFramed}
59
61
60 % Use and configure listings package for nicely formatted code
62 % Use and configure listings package for nicely formatted code
61 \usepackage{listings}
63 \usepackage{listingsutf8}
62 \lstset{
64 \lstset{
63 language=python,
65 language=python,
66 inputencoding=utf8x,
67 extendedchars=\true,
64 aboveskip=\smallskipamount,
68 aboveskip=\smallskipamount,
65 belowskip=\smallskipamount,
69 belowskip=\smallskipamount,
66 %xleftmargin=3mm,
70 %xleftmargin=3mm,
67 breaklines=true,
71 breaklines=true,
68 basicstyle=\small \ttfamily,
72 basicstyle=\small \ttfamily,
69 showstringspaces=false,
73 showstringspaces=false,
70 keywordstyle=\color{blue}\bfseries,
74 keywordstyle=\color{blue}\bfseries,
71 commentstyle=\color{myteal},
75 commentstyle=\color{myteal},
72 stringstyle=\color{darkgreen},
76 stringstyle=\color{darkgreen},
73 identifierstyle=\color{darkorange},
77 identifierstyle=\color{darkorange},
74 }
78 }
75
79
76 % The hyperref package gives us a pdf with properly built
80 % The hyperref package gives us a pdf with properly built
77 % internal navigation ('pdf bookmarks' for the table of contents,
81 % internal navigation ('pdf bookmarks' for the table of contents,
78 % internal cross-reference links, web links for URLs, etc.)
82 % internal cross-reference links, web links for URLs, etc.)
79 \usepackage{hyperref}
83 \usepackage{hyperref}
80 \hypersetup{
84 \hypersetup{
81 breaklinks=true, % so long urls are correctly broken across lines
85 breaklinks=true, % so long urls are correctly broken across lines
82 colorlinks=true,
86 colorlinks=true,
83 urlcolor=blue,
87 urlcolor=blue,
84 linkcolor=darkorange,
88 linkcolor=darkorange,
85 citecolor=darkgreen,
89 citecolor=darkgreen,
86 }
90 }
87
91
88 % hardcode size of all verbatim environments to be a bit smaller
92 % hardcode size of all verbatim environments to be a bit smaller
89 \makeatletter
93 \makeatletter
90 \g@addto@macro\@verbatim\small\topsep=0.5em\partopsep=0pt
94 \g@addto@macro\@verbatim\small\topsep=0.5em\partopsep=0pt
91 \makeatother
95 \makeatother
92
96
93 % Prevent overflowing lines due to urls and other hard-to-break entities.
97 % Prevent overflowing lines due to urls and other hard-to-break entities.
94 \sloppy
98 \sloppy
General Comments 0
You need to be logged in to leave comments. Login now