ThirdParty/Pygments/pygments/formatters/img.py

changeset 0
de9c2efb9d02
child 12
1d8dd9706f46
equal deleted inserted replaced
-1:000000000000 0:de9c2efb9d02
1 # -*- coding: utf-8 -*-
2 """
3 pygments.formatters.img
4 ~~~~~~~~~~~~~~~~~~~~~~~
5
6 Formatter for Pixmap output.
7
8 :copyright: Copyright 2006-2009 by the Pygments team, see AUTHORS.
9 :license: BSD, see LICENSE for details.
10 """
11
12 import sys
13 from commands import getstatusoutput
14
15 from pygments.formatter import Formatter
16 from pygments.util import get_bool_opt, get_int_opt, get_choice_opt
17
18 # Import this carefully
19 try:
20 import Image, ImageDraw, ImageFont
21 pil_available = True
22 except ImportError:
23 pil_available = False
24
25 try:
26 import _winreg
27 except ImportError:
28 _winreg = None
29
30 __all__ = ['ImageFormatter', 'GifImageFormatter', 'JpgImageFormatter',
31 'BmpImageFormatter']
32
33
34 # For some unknown reason every font calls it something different
35 STYLES = {
36 'NORMAL': ['', 'Roman', 'Book', 'Normal', 'Regular', 'Medium'],
37 'ITALIC': ['Oblique', 'Italic'],
38 'BOLD': ['Bold'],
39 'BOLDITALIC': ['Bold Oblique', 'Bold Italic'],
40 }
41
42 # A sane default for modern systems
43 DEFAULT_FONT_NAME_NIX = 'Bitstream Vera Sans Mono'
44 DEFAULT_FONT_NAME_WIN = 'Courier New'
45
46
47 class PilNotAvailable(ImportError):
48 """When Python imaging library is not available"""
49
50
51 class FontNotFound(Exception):
52 """When there are no usable fonts specified"""
53
54
55 class FontManager(object):
56 """
57 Manages a set of fonts: normal, italic, bold, etc...
58 """
59
60 def __init__(self, font_name, font_size=14):
61 self.font_name = font_name
62 self.font_size = font_size
63 self.fonts = {}
64 self.encoding = None
65 if sys.platform.startswith('win'):
66 if not font_name:
67 self.font_name = DEFAULT_FONT_NAME_WIN
68 self._create_win()
69 else:
70 if not font_name:
71 self.font_name = DEFAULT_FONT_NAME_NIX
72 self._create_nix()
73
74 def _get_nix_font_path(self, name, style):
75 exit, out = getstatusoutput('fc-list "%s:style=%s" file' %
76 (name, style))
77 if not exit:
78 lines = out.splitlines()
79 if lines:
80 path = lines[0].strip().strip(':')
81 return path
82
83 def _create_nix(self):
84 for name in STYLES['NORMAL']:
85 path = self._get_nix_font_path(self.font_name, name)
86 if path is not None:
87 self.fonts['NORMAL'] = ImageFont.truetype(path, self.font_size)
88 break
89 else:
90 raise FontNotFound('No usable fonts named: "%s"' %
91 self.font_name)
92 for style in ('ITALIC', 'BOLD', 'BOLDITALIC'):
93 for stylename in STYLES[style]:
94 path = self._get_nix_font_path(self.font_name, stylename)
95 if path is not None:
96 self.fonts[style] = ImageFont.truetype(path, self.font_size)
97 break
98 else:
99 if style == 'BOLDITALIC':
100 self.fonts[style] = self.fonts['BOLD']
101 else:
102 self.fonts[style] = self.fonts['NORMAL']
103
104 def _lookup_win(self, key, basename, styles, fail=False):
105 for suffix in ('', ' (TrueType)'):
106 for style in styles:
107 try:
108 valname = '%s%s%s' % (basename, style and ' '+style, suffix)
109 val, _ = _winreg.QueryValueEx(key, valname)
110 return val
111 except EnvironmentError:
112 continue
113 else:
114 if fail:
115 raise FontNotFound('Font %s (%s) not found in registry' %
116 (basename, styles[0]))
117 return None
118
119 def _create_win(self):
120 try:
121 key = _winreg.OpenKey(
122 _winreg.HKEY_LOCAL_MACHINE,
123 r'Software\Microsoft\Windows NT\CurrentVersion\Fonts')
124 except EnvironmentError:
125 try:
126 key = _winreg.OpenKey(
127 _winreg.HKEY_LOCAL_MACHINE,
128 r'Software\Microsoft\Windows\CurrentVersion\Fonts')
129 except EnvironmentError:
130 raise FontNotFound('Can\'t open Windows font registry key')
131 try:
132 path = self._lookup_win(key, self.font_name, STYLES['NORMAL'], True)
133 self.fonts['NORMAL'] = ImageFont.truetype(path, self.font_size)
134 for style in ('ITALIC', 'BOLD', 'BOLDITALIC'):
135 path = self._lookup_win(key, self.font_name, STYLES[style])
136 if path:
137 self.fonts[style] = ImageFont.truetype(path, self.font_size)
138 else:
139 if style == 'BOLDITALIC':
140 self.fonts[style] = self.fonts['BOLD']
141 else:
142 self.fonts[style] = self.fonts['NORMAL']
143 finally:
144 _winreg.CloseKey(key)
145
146 def get_char_size(self):
147 """
148 Get the character size.
149 """
150 return self.fonts['NORMAL'].getsize('M')
151
152 def get_font(self, bold, oblique):
153 """
154 Get the font based on bold and italic flags.
155 """
156 if bold and oblique:
157 return self.fonts['BOLDITALIC']
158 elif bold:
159 return self.fonts['BOLD']
160 elif oblique:
161 return self.fonts['ITALIC']
162 else:
163 return self.fonts['NORMAL']
164
165
166 class ImageFormatter(Formatter):
167 """
168 Create a PNG image from source code. This uses the Python Imaging Library to
169 generate a pixmap from the source code.
170
171 *New in Pygments 0.10.*
172
173 Additional options accepted:
174
175 `image_format`
176 An image format to output to that is recognised by PIL, these include:
177
178 * "PNG" (default)
179 * "JPEG"
180 * "BMP"
181 * "GIF"
182
183 `line_pad`
184 The extra spacing (in pixels) between each line of text.
185
186 Default: 2
187
188 `font_name`
189 The font name to be used as the base font from which others, such as
190 bold and italic fonts will be generated. This really should be a
191 monospace font to look sane.
192
193 Default: "Bitstream Vera Sans Mono"
194
195 `font_size`
196 The font size in points to be used.
197
198 Default: 14
199
200 `image_pad`
201 The padding, in pixels to be used at each edge of the resulting image.
202
203 Default: 10
204
205 `line_numbers`
206 Whether line numbers should be shown: True/False
207
208 Default: True
209
210 `line_number_step`
211 The step used when printing line numbers.
212
213 Default: 1
214
215 `line_number_bg`
216 The background colour (in "#123456" format) of the line number bar, or
217 None to use the style background color.
218
219 Default: "#eed"
220
221 `line_number_fg`
222 The text color of the line numbers (in "#123456"-like format).
223
224 Default: "#886"
225
226 `line_number_chars`
227 The number of columns of line numbers allowable in the line number
228 margin.
229
230 Default: 2
231
232 `line_number_bold`
233 Whether line numbers will be bold: True/False
234
235 Default: False
236
237 `line_number_italic`
238 Whether line numbers will be italicized: True/False
239
240 Default: False
241
242 `line_number_separator`
243 Whether a line will be drawn between the line number area and the
244 source code area: True/False
245
246 Default: True
247
248 `line_number_pad`
249 The horizontal padding (in pixels) between the line number margin, and
250 the source code area.
251
252 Default: 6
253 """
254
255 # Required by the pygments mapper
256 name = 'img'
257 aliases = ['img', 'IMG', 'png']
258 filenames = ['*.png']
259
260 unicodeoutput = False
261
262 default_image_format = 'png'
263
264 def __init__(self, **options):
265 """
266 See the class docstring for explanation of options.
267 """
268 if not pil_available:
269 raise PilNotAvailable(
270 'Python Imaging Library is required for this formatter')
271 Formatter.__init__(self, **options)
272 # Read the style
273 self.styles = dict(self.style)
274 if self.style.background_color is None:
275 self.background_color = '#fff'
276 else:
277 self.background_color = self.style.background_color
278 # Image options
279 self.image_format = get_choice_opt(
280 options, 'image_format', ['png', 'jpeg', 'gif', 'bmp'],
281 self.default_image_format, normcase=True)
282 self.image_pad = get_int_opt(options, 'image_pad', 10)
283 self.line_pad = get_int_opt(options, 'line_pad', 2)
284 # The fonts
285 fontsize = get_int_opt(options, 'font_size', 14)
286 self.fonts = FontManager(options.get('font_name', ''), fontsize)
287 self.fontw, self.fonth = self.fonts.get_char_size()
288 # Line number options
289 self.line_number_fg = options.get('line_number_fg', '#886')
290 self.line_number_bg = options.get('line_number_bg', '#eed')
291 self.line_number_chars = get_int_opt(options,
292 'line_number_chars', 2)
293 self.line_number_bold = get_bool_opt(options,
294 'line_number_bold', False)
295 self.line_number_italic = get_bool_opt(options,
296 'line_number_italic', False)
297 self.line_number_pad = get_int_opt(options, 'line_number_pad', 6)
298 self.line_numbers = get_bool_opt(options, 'line_numbers', True)
299 self.line_number_separator = get_bool_opt(options,
300 'line_number_separator', True)
301 self.line_number_step = get_int_opt(options, 'line_number_step', 1)
302 if self.line_numbers:
303 self.line_number_width = (self.fontw * self.line_number_chars +
304 self.line_number_pad * 2)
305 else:
306 self.line_number_width = 0
307 self.drawables = []
308
309 def get_style_defs(self, arg=''):
310 raise NotImplementedError('The -S option is meaningless for the image '
311 'formatter. Use -O style=<stylename> instead.')
312
313 def _get_line_height(self):
314 """
315 Get the height of a line.
316 """
317 return self.fonth + self.line_pad
318
319 def _get_line_y(self, lineno):
320 """
321 Get the Y coordinate of a line number.
322 """
323 return lineno * self._get_line_height() + self.image_pad
324
325 def _get_char_width(self):
326 """
327 Get the width of a character.
328 """
329 return self.fontw
330
331 def _get_char_x(self, charno):
332 """
333 Get the X coordinate of a character position.
334 """
335 return charno * self.fontw + self.image_pad + self.line_number_width
336
337 def _get_text_pos(self, charno, lineno):
338 """
339 Get the actual position for a character and line position.
340 """
341 return self._get_char_x(charno), self._get_line_y(lineno)
342
343 def _get_linenumber_pos(self, lineno):
344 """
345 Get the actual position for the start of a line number.
346 """
347 return (self.image_pad, self._get_line_y(lineno))
348
349 def _get_text_color(self, style):
350 """
351 Get the correct color for the token from the style.
352 """
353 if style['color'] is not None:
354 fill = '#' + style['color']
355 else:
356 fill = '#000'
357 return fill
358
359 def _get_style_font(self, style):
360 """
361 Get the correct font for the style.
362 """
363 return self.fonts.get_font(style['bold'], style['italic'])
364
365 def _get_image_size(self, maxcharno, maxlineno):
366 """
367 Get the required image size.
368 """
369 return (self._get_char_x(maxcharno) + self.image_pad,
370 self._get_line_y(maxlineno + 0) + self.image_pad)
371
372 def _draw_linenumber(self, lineno):
373 """
374 Remember a line number drawable to paint later.
375 """
376 self._draw_text(
377 self._get_linenumber_pos(lineno),
378 str(lineno + 1).rjust(self.line_number_chars),
379 font=self.fonts.get_font(self.line_number_bold,
380 self.line_number_italic),
381 fill=self.line_number_fg,
382 )
383
384 def _draw_text(self, pos, text, font, **kw):
385 """
386 Remember a single drawable tuple to paint later.
387 """
388 self.drawables.append((pos, text, font, kw))
389
390 def _create_drawables(self, tokensource):
391 """
392 Create drawables for the token content.
393 """
394 lineno = charno = maxcharno = 0
395 for ttype, value in tokensource:
396 while ttype not in self.styles:
397 ttype = ttype.parent
398 style = self.styles[ttype]
399 # TODO: make sure tab expansion happens earlier in the chain. It
400 # really ought to be done on the input, as to do it right here is
401 # quite complex.
402 value = value.expandtabs(4)
403 lines = value.splitlines(True)
404 #print lines
405 for i, line in enumerate(lines):
406 temp = line.rstrip('\n')
407 if temp:
408 self._draw_text(
409 self._get_text_pos(charno, lineno),
410 temp,
411 font = self._get_style_font(style),
412 fill = self._get_text_color(style)
413 )
414 charno += len(temp)
415 maxcharno = max(maxcharno, charno)
416 if line.endswith('\n'):
417 # add a line for each extra line in the value
418 charno = 0
419 lineno += 1
420 self.maxcharno = maxcharno
421 self.maxlineno = lineno
422
423 def _draw_line_numbers(self):
424 """
425 Create drawables for the line numbers.
426 """
427 if not self.line_numbers:
428 return
429 for i in xrange(self.maxlineno):
430 if ((i + 1) % self.line_number_step) == 0:
431 self._draw_linenumber(i)
432
433 def _paint_line_number_bg(self, im):
434 """
435 Paint the line number background on the image.
436 """
437 if not self.line_numbers:
438 return
439 if self.line_number_fg is None:
440 return
441 draw = ImageDraw.Draw(im)
442 recth = im.size[-1]
443 rectw = self.image_pad + self.line_number_width - self.line_number_pad
444 draw.rectangle([(0, 0),
445 (rectw, recth)],
446 fill=self.line_number_bg)
447 draw.line([(rectw, 0), (rectw, recth)], fill=self.line_number_fg)
448 del draw
449
450 def format(self, tokensource, outfile):
451 """
452 Format ``tokensource``, an iterable of ``(tokentype, tokenstring)``
453 tuples and write it into ``outfile``.
454
455 This implementation calculates where it should draw each token on the
456 pixmap, then calculates the required pixmap size and draws the items.
457 """
458 self._create_drawables(tokensource)
459 self._draw_line_numbers()
460 im = Image.new(
461 'RGB',
462 self._get_image_size(self.maxcharno, self.maxlineno),
463 self.background_color
464 )
465 self._paint_line_number_bg(im)
466 draw = ImageDraw.Draw(im)
467 for pos, value, font, kw in self.drawables:
468 draw.text(pos, value, font=font, **kw)
469 im.save(outfile, self.image_format.upper())
470
471
472 # Add one formatter per format, so that the "-f gif" option gives the correct result
473 # when used in pygmentize.
474
475 class GifImageFormatter(ImageFormatter):
476 """
477 Create a GIF image from source code. This uses the Python Imaging Library to
478 generate a pixmap from the source code.
479
480 *New in Pygments 1.0.* (You could create GIF images before by passing a
481 suitable `image_format` option to the `ImageFormatter`.)
482 """
483
484 name = 'img_gif'
485 aliases = ['gif']
486 filenames = ['*.gif']
487 default_image_format = 'gif'
488
489
490 class JpgImageFormatter(ImageFormatter):
491 """
492 Create a JPEG image from source code. This uses the Python Imaging Library to
493 generate a pixmap from the source code.
494
495 *New in Pygments 1.0.* (You could create JPEG images before by passing a
496 suitable `image_format` option to the `ImageFormatter`.)
497 """
498
499 name = 'img_jpg'
500 aliases = ['jpg', 'jpeg']
501 filenames = ['*.jpg']
502 default_image_format = 'jpeg'
503
504
505 class BmpImageFormatter(ImageFormatter):
506 """
507 Create a bitmap image from source code. This uses the Python Imaging Library to
508 generate a pixmap from the source code.
509
510 *New in Pygments 1.0.* (You could create bitmap images before by passing a
511 suitable `image_format` option to the `ImageFormatter`.)
512 """
513
514 name = 'img_bmp'
515 aliases = ['bmp', 'bitmap']
516 filenames = ['*.bmp']
517 default_image_format = 'bmp'

eric ide

mercurial