1
0
mirror of https://github.com/gryf/urxvt-wrapper.git synced 2025-12-17 11:30:21 +01:00

Introducing Font and Urxvt classes.

Currently, all of the functionality lies at most in functions. Now it is
easier for dividing responsibility between the Font and Urxvt classes,
and easier to understand what is going on.
This commit is contained in:
2021-02-19 18:37:05 +01:00
parent df9deffe85
commit a94f03878a

281
urxvt.py
View File

@@ -74,104 +74,233 @@ class Logger:
LOG = Logger(__name__)() LOG = Logger(__name__)()
# Arbitrary added fonts, that provides symbols, icons, emoji (besides those in
# Nerd Font)
_ADDITIONAL_FONTS = ['Symbola', 'Unifont Upper', 'DejaVu Sans']
_REGULAR = ['regular', 'normal', 'book', 'medium']
_XFT_TEMPLATE = 'xft:%s:style=%s:pixelsize=%d'
class Font:
"""
Represents font object, which as it repr will be a string, like
xft:font name:style=somestyle:pixelsize=12
def _parse_style(styles): It might be seen in two variants: regular and bold.
for reg_style in _REGULAR:
if reg_style in ''.join(styles).lower(): """
XFT_TEMPLATE = 'xft:%s:style=%s:pixelsize=%d'
_REGULAR = ['regular', 'normal', 'book', 'medium']
_BOLD = ['bold']
_AVAILABLE_FONTS = {}
def __init__(self, name, size):
self.size = size
self.name = name
self._regular = None
self._bold = None
self._get_all_suitable_fonts()
@property
def bold(self):
"""
Return full string font to use for xft definition for urxvt, i.e.
xft:font name:style=Bold:pixelsize=20
It may happen, that a font doesn't provide bold face. Function will
than return empty string.
"""
if self._bold is not None:
return self._bold
style = Font._AVAILABLE_FONTS['bold'].get(self.name)
if not style:
LOG.warning(f'Bold style not found for {self.name}')
self._bold = ''
else:
self._bold = Font.XFT_TEMPLATE % (self.name, style, self.size)
return self._bold
@property
def regular(self):
"""
Return full string font to use for xft definition for urxvt, i.e.
xft:font name:style=Regular:pixelsize=20
Font style depends on the particular font, and can be one of case
insensitive: regular, normal, book, medium.
Technicly, medium style should be semi-bold or bold, but there is at
least one font face which treats medium style as regular one.
"""
if self._regular is not None:
return self._regular
style = Font._AVAILABLE_FONTS['regular'].get(self.name)
if not style:
LOG.warning(f'Regular style not found for {self.name}')
self._regular = ''
else:
self._regular = Font.XFT_TEMPLATE % (self.name, style, self.size)
return self._regular
def _get_all_suitable_fonts(self):
"""
Scan all available in the system fonts, where every line have format:
font_filename: font_name1[,font_name2,…]:style=style1[,style2,…]
Font can have several names and several styles. Styles can be either
single defined style or comma separated list of aliases or
internationalized names for style, i.e.:
filename1: font_name1:style=style
filename2: font_name2,font_name3:style=style1,style2
filename3: font_name4:style=style3,style4
Return a dictionary of styles associated to the font name, i.e.:
{font_name1: (style),
font_name2: (style1, style2),
font_name3: (style1, style2),
font_name4: (style3, style4)}
"""
if Font._AVAILABLE_FONTS:
return
regular = {}
bold = {}
out = subprocess.check_output(['fc-list']).decode('utf-8')
out = sorted(out.split('\n')) # let's have it in order
for line in out:
if not line:
continue
if ': ' not in line:
continue
if ':style=' not in line:
continue
line = line.split(': ')[1]
font_names = [n.strip() for n in line.split(':')[0].split(',')]
styles = [s.strip() for s in line.split(':style=')[1].split(',')]
style = self._parse_style(styles)
if not style:
LOG.debug('No suitable styles found for font in line: %s',
line)
continue
for name in font_names:
if style.lower() == 'bold' and not bold.get(name):
LOG.info('Adding bold font for name: %s', name)
bold[name] = style
elif style.lower() != 'bold' and not regular.get(name):
LOG.info('Adding regular font for name: %s', name)
regular[name] = style
else:
LOG.debug('Font %s probably already exists in dict', name)
Font._AVAILABLE_FONTS = {'regular': regular, 'bold': bold}
def _parse_style(self, styles):
for reg_style in Font._REGULAR:
if reg_style in ''.join(styles).lower():
for style in styles:
if style.lower() == reg_style:
return style
if 'bold' in ''.join(styles).lower():
for style in styles: for style in styles:
if style.lower() == reg_style: if style.lower() == 'bold':
return style return style
if 'bold' in ''.join(styles).lower():
for style in styles:
if style.lower() == 'bold':
return style
class Urxvt:
def _get_all_suitable_fonts():
""" """
Scan all available in the system fonts, where every line have format: Runner for the urxvt
font_filename: font_name1[,font_name2,…]:style=style1[,style2,…]
Font can have several names and several styles. Styles can be either
single defined style or comma separated list of aliases or
internationalized names for style, i.e.:
filename1: font_name1:style=style
filename2: font_name2,font_name3:style=style1,style2
filename3: font_name4:style=style3,style4
Return a dictionary of styles associated to the font name, i.e.:
{font_name1: (style),
font_name2: (style1, style2),
font_name3: (style1, style2),
font_name4: (style3, style4)}
""" """
regular = {} # Arbitrary added fonts, that provides symbols, icons, emoji (besides
bold = {} # those in defualt font)
_ADDITIONAL_FONTS = ['Symbola', 'Unifont Upper', 'DejaVu Sans']
out = subprocess.check_output(['fc-list']).decode('utf-8') def __init__(self, args):
self.size = args.size
self.icon = args.icon
self.fonts = None
self.perl_extensions = None
for line in out.split('\n'): self._bitmap = args.bitmap
if not line: self._icon_path = ICON_PATH
continue self._exec = args.execute
if ': ' not in line: self._setup(args)
continue self._validate()
if ':style=' not in line: def run(self):
continue args = self._make_command_args()
line = line.split(': ')[1] LOG.info('%s', args)
font_names = [n.strip() for n in line.split(':')[0].split(',')] # subprocess.run(command)
styles = [s.strip() for s in line.split(':style=')[1].split(',')] # LOG.info('%s', command)
style = _parse_style(styles) def _setup(self, args):
if not style: # it could be a list or a single font, it will be combined with
LOG.debug('No suitable styles found for font in line: %s', line) # additional fonts
continue self.fonts = self._parse_fonts(args.default_font)
for name in font_names: if not args.no_perl:
if style.lower() == 'bold' and not bold.get(name): self.perl_extensions = PERLEXT
bold[name] = style if args.tabbedalt:
elif style.lower() != 'bold' and not regular.get(name): self.perl_extensions = 'tabbedalt,' + PERLEXT
regular[name] = style
else:
LOG.debug('Font %s probably already exists in dict', name)
return {'regular': regular, 'bold': bold} def _validate(self):
# vaildate fonts
for font in self.fonts:
if not any((font.regular, font.bold)):
LOG.error('Font %s seems to be unusable or nonexistent.',
font.name)
def _make_command_args(self):
args = []
_AVAILABLE_FONTS = _get_all_suitable_fonts() if self.perl_extensions:
args.extend(['-pe', self.perl_extensions])
args.extend(['-fn',
','.join([f.regular for f in self.fonts if f.regular])])
args.extend(['-fb', ','.join([f.bold for f in self.fonts if f.bold])])
args.extend(['-icon', os.path.join(ICON_PATH, self.icon)])
def _get_style(name, bold=False): if self._exec:
key = 'bold' if bold else 'regular' args.extend(['-exec', self._exec])
try:
return _AVAILABLE_FONTS[key][name]
except KeyError:
LOG.warning('There is no matching font for name "%s" for style %s.',
name, key)
return None
return args
def _get_font_list(ff_list, size, bold=False): def _parse_fonts(self, font_string):
fonts = [] """
for face in ff_list: Parse potentially provided font list, add additional fonts to it,
style = _get_style(face, bold) adjust for possible bitmap font and return a list of Font objects.
if not style: """
continue # get the copy of affitional fonts
fonts.append(_XFT_TEMPLATE % (face, _get_style(face, bold), size)) font_faces = Urxvt._ADDITIONAL_FONTS[:]
return ','.join(fonts)
# find out main font/fonts passed by commandline/env/default
if ',' in font_string:
f_f = [f.strip() for f in font_string.split(',')]
f_f.extend(font_faces)
font_faces = f_f
else:
font_faces.insert(0, font_string)
if self._bitmap:
font_faces.insert(0, DEFAULT_BITMAP)
# return list of Font objects
return [Font(f, self.size) for f in font_faces]
def main(): def main():