]>
jfr.im git - dlqueue.git/blob - venv/lib/python3.11/site-packages/pip/_vendor/pygments/formatters/img.py
2 pygments.formatters.img
3 ~~~~~~~~~~~~~~~~~~~~~~~
5 Formatter for Pixmap output.
7 :copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS.
8 :license: BSD, see LICENSE for details.
14 from pip
._vendor
.pygments
.formatter
import Formatter
15 from pip
._vendor
.pygments
.util
import get_bool_opt
, get_int_opt
, get_list_opt
, \
20 # Import this carefully
22 from PIL
import Image
, ImageDraw
, ImageFont
31 import winreg
as _winreg
35 __all__
= ['ImageFormatter', 'GifImageFormatter', 'JpgImageFormatter',
39 # For some unknown reason every font calls it something different
41 'NORMAL': ['', 'Roman', 'Book', 'Normal', 'Regular', 'Medium'],
42 'ITALIC': ['Oblique', 'Italic'],
44 'BOLDITALIC': ['Bold Oblique', 'Bold Italic'],
47 # A sane default for modern systems
48 DEFAULT_FONT_NAME_NIX
= 'DejaVu Sans Mono'
49 DEFAULT_FONT_NAME_WIN
= 'Courier New'
50 DEFAULT_FONT_NAME_MAC
= 'Menlo'
53 class PilNotAvailable(ImportError):
54 """When Python imaging library is not available"""
57 class FontNotFound(Exception):
58 """When there are no usable fonts specified"""
63 Manages a set of fonts: normal, italic, bold, etc...
66 def __init__(self
, font_name
, font_size
=14):
67 self
.font_name
= font_name
68 self
.font_size
= font_size
71 if sys
.platform
.startswith('win'):
73 self
.font_name
= DEFAULT_FONT_NAME_WIN
75 elif sys
.platform
.startswith('darwin'):
77 self
.font_name
= DEFAULT_FONT_NAME_MAC
81 self
.font_name
= DEFAULT_FONT_NAME_NIX
84 def _get_nix_font_path(self
, name
, style
):
85 proc
= subprocess
.Popen(['fc-list', "%s:style=%s" % (name
, style
), 'file'],
86 stdout
=subprocess
.PIPE
, stderr
=None)
87 stdout
, _
= proc
.communicate()
88 if proc
.returncode
== 0:
89 lines
= stdout
.splitlines()
91 if line
.startswith(b
'Fontconfig warning:'):
93 path
= line
.decode().strip().strip(':')
98 def _create_nix(self
):
99 for name
in STYLES
['NORMAL']:
100 path
= self
._get
_nix
_font
_path
(self
.font_name
, name
)
102 self
.fonts
['NORMAL'] = ImageFont
.truetype(path
, self
.font_size
)
105 raise FontNotFound('No usable fonts named: "%s"' %
107 for style
in ('ITALIC', 'BOLD', 'BOLDITALIC'):
108 for stylename
in STYLES
[style
]:
109 path
= self
._get
_nix
_font
_path
(self
.font_name
, stylename
)
111 self
.fonts
[style
] = ImageFont
.truetype(path
, self
.font_size
)
114 if style
== 'BOLDITALIC':
115 self
.fonts
[style
] = self
.fonts
['BOLD']
117 self
.fonts
[style
] = self
.fonts
['NORMAL']
119 def _get_mac_font_path(self
, font_map
, name
, style
):
120 return font_map
.get((name
+ ' ' + style
).strip().lower())
122 def _create_mac(self
):
124 for font_dir
in (os
.path
.join(os
.getenv("HOME"), 'Library/Fonts/'),
125 '/Library/Fonts/', '/System/Library/Fonts/'):
127 (os
.path
.splitext(f
)[0].lower(), os
.path
.join(font_dir
, f
))
128 for f
in os
.listdir(font_dir
)
129 if f
.lower().endswith(('ttf', 'ttc')))
131 for name
in STYLES
['NORMAL']:
132 path
= self
._get
_mac
_font
_path
(font_map
, self
.font_name
, name
)
134 self
.fonts
['NORMAL'] = ImageFont
.truetype(path
, self
.font_size
)
137 raise FontNotFound('No usable fonts named: "%s"' %
139 for style
in ('ITALIC', 'BOLD', 'BOLDITALIC'):
140 for stylename
in STYLES
[style
]:
141 path
= self
._get
_mac
_font
_path
(font_map
, self
.font_name
, stylename
)
143 self
.fonts
[style
] = ImageFont
.truetype(path
, self
.font_size
)
146 if style
== 'BOLDITALIC':
147 self
.fonts
[style
] = self
.fonts
['BOLD']
149 self
.fonts
[style
] = self
.fonts
['NORMAL']
151 def _lookup_win(self
, key
, basename
, styles
, fail
=False):
152 for suffix
in ('', ' (TrueType)'):
155 valname
= '%s%s%s' % (basename
, style
and ' '+style
, suffix
)
156 val
, _
= _winreg
.QueryValueEx(key
, valname
)
162 raise FontNotFound('Font %s (%s) not found in registry' %
163 (basename
, styles
[0]))
166 def _create_win(self
):
168 keynames
= [ (_winreg
.HKEY_CURRENT_USER
, r
'Software\Microsoft\Windows NT\CurrentVersion\Fonts'),
169 (_winreg
.HKEY_CURRENT_USER
, r
'Software\Microsoft\Windows\CurrentVersion\Fonts'),
170 (_winreg
.HKEY_LOCAL_MACHINE
, r
'Software\Microsoft\Windows NT\CurrentVersion\Fonts'),
171 (_winreg
.HKEY_LOCAL_MACHINE
, r
'Software\Microsoft\Windows\CurrentVersion\Fonts') ]
172 for keyname
in keynames
:
174 key
= _winreg
.OpenKey(*keyname
)
176 path
= self
._lookup
_win
(key
, self
.font_name
, STYLES
['NORMAL'], True)
177 self
.fonts
['NORMAL'] = ImageFont
.truetype(path
, self
.font_size
)
178 for style
in ('ITALIC', 'BOLD', 'BOLDITALIC'):
179 path
= self
._lookup
_win
(key
, self
.font_name
, STYLES
[style
])
181 self
.fonts
[style
] = ImageFont
.truetype(path
, self
.font_size
)
183 if style
== 'BOLDITALIC':
184 self
.fonts
[style
] = self
.fonts
['BOLD']
186 self
.fonts
[style
] = self
.fonts
['NORMAL']
188 except FontNotFound
as err
:
191 _winreg
.CloseKey(key
)
195 # If we get here, we checked all registry keys and had no luck
196 # We can be in one of two situations now:
197 # * All key lookups failed. In this case lookuperror is None and we
198 # will raise a generic error
199 # * At least one lookup failed with a FontNotFound error. In this
200 # case, we will raise that as a more specific error
203 raise FontNotFound('Can\'t open Windows font registry key')
205 def get_char_size(self
):
207 Get the character size.
209 return self
.get_text_size('M')
211 def get_text_size(self
, text
):
213 Get the text size (width, height).
215 font
= self
.fonts
['NORMAL']
216 if hasattr(font
, 'getbbox'): # Pillow >= 9.2.0
217 return font
.getbbox(text
)[2:4]
219 return font
.getsize(text
)
221 def get_font(self
, bold
, oblique
):
223 Get the font based on bold and italic flags.
226 return self
.fonts
['BOLDITALIC']
228 return self
.fonts
['BOLD']
230 return self
.fonts
['ITALIC']
232 return self
.fonts
['NORMAL']
235 class ImageFormatter(Formatter
):
237 Create a PNG image from source code. This uses the Python Imaging Library to
238 generate a pixmap from the source code.
240 .. versionadded:: 0.10
242 Additional options accepted:
245 An image format to output to that is recognised by PIL, these include:
253 The extra spacing (in pixels) between each line of text.
258 The font name to be used as the base font from which others, such as
259 bold and italic fonts will be generated. This really should be a
260 monospace font to look sane.
262 Default: "Courier New" on Windows, "Menlo" on Mac OS, and
263 "DejaVu Sans Mono" on \\*nix
266 The font size in points to be used.
271 The padding, in pixels to be used at each edge of the resulting image.
276 Whether line numbers should be shown: True/False
281 The line number of the first line.
286 The step used when printing line numbers.
291 The background colour (in "#123456" format) of the line number bar, or
292 None to use the style background color.
297 The text color of the line numbers (in "#123456"-like format).
302 The number of columns of line numbers allowable in the line number
308 Whether line numbers will be bold: True/False
313 Whether line numbers will be italicized: True/False
317 `line_number_separator`
318 Whether a line will be drawn between the line number area and the
319 source code area: True/False
324 The horizontal padding (in pixels) between the line number margin, and
325 the source code area.
330 Specify a list of lines to be highlighted.
332 .. versionadded:: 1.2
337 Specify the color for highlighting lines.
339 .. versionadded:: 1.2
341 Default: highlight color of the selected style
344 # Required by the pygments mapper
346 aliases
= ['img', 'IMG', 'png']
347 filenames
= ['*.png']
349 unicodeoutput
= False
351 default_image_format
= 'png'
353 def __init__(self
, **options
):
355 See the class docstring for explanation of options.
357 if not pil_available
:
358 raise PilNotAvailable(
359 'Python Imaging Library is required for this formatter')
360 Formatter
.__init
__(self
, **options
)
361 self
.encoding
= 'latin1' # let pygments.format() do the right thing
363 self
.styles
= dict(self
.style
)
364 if self
.style
.background_color
is None:
365 self
.background_color
= '#fff'
367 self
.background_color
= self
.style
.background_color
369 self
.image_format
= get_choice_opt(
370 options
, 'image_format', ['png', 'jpeg', 'gif', 'bmp'],
371 self
.default_image_format
, normcase
=True)
372 self
.image_pad
= get_int_opt(options
, 'image_pad', 10)
373 self
.line_pad
= get_int_opt(options
, 'line_pad', 2)
375 fontsize
= get_int_opt(options
, 'font_size', 14)
376 self
.fonts
= FontManager(options
.get('font_name', ''), fontsize
)
377 self
.fontw
, self
.fonth
= self
.fonts
.get_char_size()
378 # Line number options
379 self
.line_number_fg
= options
.get('line_number_fg', '#886')
380 self
.line_number_bg
= options
.get('line_number_bg', '#eed')
381 self
.line_number_chars
= get_int_opt(options
,
382 'line_number_chars', 2)
383 self
.line_number_bold
= get_bool_opt(options
,
384 'line_number_bold', False)
385 self
.line_number_italic
= get_bool_opt(options
,
386 'line_number_italic', False)
387 self
.line_number_pad
= get_int_opt(options
, 'line_number_pad', 6)
388 self
.line_numbers
= get_bool_opt(options
, 'line_numbers', True)
389 self
.line_number_separator
= get_bool_opt(options
,
390 'line_number_separator', True)
391 self
.line_number_step
= get_int_opt(options
, 'line_number_step', 1)
392 self
.line_number_start
= get_int_opt(options
, 'line_number_start', 1)
393 if self
.line_numbers
:
394 self
.line_number_width
= (self
.fontw
* self
.line_number_chars
+
395 self
.line_number_pad
* 2)
397 self
.line_number_width
= 0
399 hl_lines_str
= get_list_opt(options
, 'hl_lines', [])
400 for line
in hl_lines_str
:
402 self
.hl_lines
.append(int(line
))
405 self
.hl_color
= options
.get('hl_color',
406 self
.style
.highlight_color
) or '#f90'
409 def get_style_defs(self
, arg
=''):
410 raise NotImplementedError('The -S option is meaningless for the image '
411 'formatter. Use -O style=<stylename> instead.')
413 def _get_line_height(self
):
415 Get the height of a line.
417 return self
.fonth
+ self
.line_pad
419 def _get_line_y(self
, lineno
):
421 Get the Y coordinate of a line number.
423 return lineno
* self
._get
_line
_height
() + self
.image_pad
425 def _get_char_width(self
):
427 Get the width of a character.
431 def _get_char_x(self
, linelength
):
433 Get the X coordinate of a character position.
435 return linelength
+ self
.image_pad
+ self
.line_number_width
437 def _get_text_pos(self
, linelength
, lineno
):
439 Get the actual position for a character and line position.
441 return self
._get
_char
_x
(linelength
), self
._get
_line
_y
(lineno
)
443 def _get_linenumber_pos(self
, lineno
):
445 Get the actual position for the start of a line number.
447 return (self
.image_pad
, self
._get
_line
_y
(lineno
))
449 def _get_text_color(self
, style
):
451 Get the correct color for the token from the style.
453 if style
['color'] is not None:
454 fill
= '#' + style
['color']
459 def _get_text_bg_color(self
, style
):
461 Get the correct background color for the token from the style.
463 if style
['bgcolor'] is not None:
464 bg_color
= '#' + style
['bgcolor']
469 def _get_style_font(self
, style
):
471 Get the correct font for the style.
473 return self
.fonts
.get_font(style
['bold'], style
['italic'])
475 def _get_image_size(self
, maxlinelength
, maxlineno
):
477 Get the required image size.
479 return (self
._get
_char
_x
(maxlinelength
) + self
.image_pad
,
480 self
._get
_line
_y
(maxlineno
+ 0) + self
.image_pad
)
482 def _draw_linenumber(self
, posno
, lineno
):
484 Remember a line number drawable to paint later.
487 self
._get
_linenumber
_pos
(posno
),
488 str(lineno
).rjust(self
.line_number_chars
),
489 font
=self
.fonts
.get_font(self
.line_number_bold
,
490 self
.line_number_italic
),
491 text_fg
=self
.line_number_fg
,
495 def _draw_text(self
, pos
, text
, font
, text_fg
, text_bg
):
497 Remember a single drawable tuple to paint later.
499 self
.drawables
.append((pos
, text
, font
, text_fg
, text_bg
))
501 def _create_drawables(self
, tokensource
):
503 Create drawables for the token content.
505 lineno
= charno
= maxcharno
= 0
506 maxlinelength
= linelength
= 0
507 for ttype
, value
in tokensource
:
508 while ttype
not in self
.styles
:
510 style
= self
.styles
[ttype
]
511 # TODO: make sure tab expansion happens earlier in the chain. It
512 # really ought to be done on the input, as to do it right here is
514 value
= value
.expandtabs(4)
515 lines
= value
.splitlines(True)
517 for i
, line
in enumerate(lines
):
518 temp
= line
.rstrip('\n')
521 self
._get
_text
_pos
(linelength
, lineno
),
523 font
= self
._get
_style
_font
(style
),
524 text_fg
= self
._get
_text
_color
(style
),
525 text_bg
= self
._get
_text
_bg
_color
(style
),
527 temp_width
, _
= self
.fonts
.get_text_size(temp
)
528 linelength
+= temp_width
529 maxlinelength
= max(maxlinelength
, linelength
)
531 maxcharno
= max(maxcharno
, charno
)
532 if line
.endswith('\n'):
533 # add a line for each extra line in the value
537 self
.maxlinelength
= maxlinelength
538 self
.maxcharno
= maxcharno
539 self
.maxlineno
= lineno
541 def _draw_line_numbers(self
):
543 Create drawables for the line numbers.
545 if not self
.line_numbers
:
547 for p
in range(self
.maxlineno
):
548 n
= p
+ self
.line_number_start
549 if (n
% self
.line_number_step
) == 0:
550 self
._draw
_linenumber
(p
, n
)
552 def _paint_line_number_bg(self
, im
):
554 Paint the line number background on the image.
556 if not self
.line_numbers
:
558 if self
.line_number_fg
is None:
560 draw
= ImageDraw
.Draw(im
)
562 rectw
= self
.image_pad
+ self
.line_number_width
- self
.line_number_pad
563 draw
.rectangle([(0, 0), (rectw
, recth
)],
564 fill
=self
.line_number_bg
)
565 if self
.line_number_separator
:
566 draw
.line([(rectw
, 0), (rectw
, recth
)], fill
=self
.line_number_fg
)
569 def format(self
, tokensource
, outfile
):
571 Format ``tokensource``, an iterable of ``(tokentype, tokenstring)``
572 tuples and write it into ``outfile``.
574 This implementation calculates where it should draw each token on the
575 pixmap, then calculates the required pixmap size and draws the items.
577 self
._create
_drawables
(tokensource
)
578 self
._draw
_line
_numbers
()
581 self
._get
_image
_size
(self
.maxlinelength
, self
.maxlineno
),
582 self
.background_color
584 self
._paint
_line
_number
_bg
(im
)
585 draw
= ImageDraw
.Draw(im
)
588 x
= self
.image_pad
+ self
.line_number_width
- self
.line_number_pad
+ 1
589 recth
= self
._get
_line
_height
()
590 rectw
= im
.size
[0] - x
591 for linenumber
in self
.hl_lines
:
592 y
= self
._get
_line
_y
(linenumber
- 1)
593 draw
.rectangle([(x
, y
), (x
+ rectw
, y
+ recth
)],
595 for pos
, value
, font
, text_fg
, text_bg
in self
.drawables
:
597 text_size
= draw
.textsize(text
=value
, font
=font
)
598 draw
.rectangle([pos
[0], pos
[1], pos
[0] + text_size
[0], pos
[1] + text_size
[1]], fill
=text_bg
)
599 draw
.text(pos
, value
, font
=font
, fill
=text_fg
)
600 im
.save(outfile
, self
.image_format
.upper())
603 # Add one formatter per format, so that the "-f gif" option gives the correct result
604 # when used in pygmentize.
606 class GifImageFormatter(ImageFormatter
):
608 Create a GIF image from source code. This uses the Python Imaging Library to
609 generate a pixmap from the source code.
611 .. versionadded:: 1.0
616 filenames
= ['*.gif']
617 default_image_format
= 'gif'
620 class JpgImageFormatter(ImageFormatter
):
622 Create a JPEG image from source code. This uses the Python Imaging Library to
623 generate a pixmap from the source code.
625 .. versionadded:: 1.0
629 aliases
= ['jpg', 'jpeg']
630 filenames
= ['*.jpg']
631 default_image_format
= 'jpeg'
634 class BmpImageFormatter(ImageFormatter
):
636 Create a bitmap image from source code. This uses the Python Imaging Library to
637 generate a pixmap from the source code.
639 .. versionadded:: 1.0
643 aliases
= ['bmp', 'bitmap']
644 filenames
= ['*.bmp']
645 default_image_format
= 'bmp'