Expand the renderer options, and make a way to programmatically generate the styles easily.

This commit is contained in:
Raoul Snyman 2021-08-02 13:55:10 -07:00
parent 7222a5918b
commit 17e5d7aa0e

View File

@ -4,9 +4,9 @@ CHORD = '<td class="chord">{}</td>'
SYLLB = '<td class="syllable">{}</td>' SYLLB = '<td class="syllable">{}</td>'
LINE = '<table class="line" border="0" cellpadding="0" cellspacing="0"><tr class="chords-line">{}</tr><tr ' \ LINE = '<table class="line" border="0" cellpadding="0" cellspacing="0"><tr class="chords-line">{}</tr><tr ' \
'class="lyrics-line">{}</tr></table>' 'class="lyrics-line">{}</tr></table>'
VERSE = '''<section class="verse"> VERSE = '''<section class="stanza {verse_type}">
<h4 class="verse-name {verse_type}">{verse_name}</h4> <h4 class="stanza-heading {verse_type}-heading">{verse_name}</h4>
<div class="verse-body"> <div class="stanza {verse_type}">
{verse_body} {verse_body}
</div> </div>
</section>''' </section>'''
@ -15,10 +15,7 @@ HTML = '''<html>
<head> <head>
<title>{title}</title> <title>{title}</title>
<style> <style>
h4.verse-name {{ font-weight: normal; }} h1.title, h4.stanza-heading {{ font-weight: normal; }}
.verse-name {{ margin-bottom: 0; }}
.verse-body {{ padding-left: 2rem; }}
.chord {{ text-align: center; }}
{styles} {styles}
</style> </style>
</head> </head>
@ -26,263 +23,329 @@ HTML = '''<html>
{body} {body}
</body> </body>
</html>''' </html>'''
STYLES = { STYLE_KEYS = [
'verse_name_bold': '.verse-name { font-weight: bold !important; }', 'font',
'verse_upper': '.verse-name { text-transform: uppercase; }', 'size',
'chord_bold': '.chord { font-weight: bold; }' 'is_bold',
} 'is_centered',
'is_upper',
'indent'
]
HTML_OPTIONS = { HTML_OPTIONS = {
'default_font': { 'default_font': {
'description': 'The default font to use.', 'description': 'The default font to use.',
'type': str, 'type': str,
'default': None 'default': None,
'group': 'default'
}, },
'default_size': { 'default_size': {
'description': 'The default size to use.', 'description': 'The default size to use.',
'type': int, 'type': int,
'default': None 'default': None,
'group': 'default'
}, },
'title_font': { 'title_font': {
'description': 'The font for the title', 'description': 'The font for the title',
'type': str, 'type': str,
'default': None 'default': None,
'group': 'title'
}, },
'title_size': { 'title_size': {
'description': 'The size of the title in rem', 'description': 'The size of the title in rem',
'type': int, 'type': int,
'default': None 'default': None,
'group': 'title'
}, },
'title_is_bold': { 'title_is_bold': {
'description': 'Set to True to make the title bold', 'description': 'Set to True to make the title bold',
'type': bool, 'type': bool,
'default': True 'default': True,
'group': 'title'
},
'title_is_centered': {
'description': 'Center the title on the page',
'type': bool,
'default': None,
'group': 'title'
}, },
'title_is_upper': { 'title_is_upper': {
'description': 'Force the title to be uppercase', 'description': 'Force the title to be uppercase',
'type': bool, 'type': bool,
'default': None, 'default': None,
'group': 'title'
}, },
'composer_font': { 'composer_font': {
'description': 'The font for the composer', 'description': 'The font for the composer',
'type': str, 'type': str,
'default': None 'default': None,
'group': 'composer'
}, },
'composer_size': { 'composer_size': {
'description': 'The size of the composer in rem', 'description': 'The size of the composer in rem',
'type': int, 'type': int,
'default': None 'default': None,
'group': 'composer'
}, },
'composer_is_bold': { 'composer_is_bold': {
'description': 'Set to True to make the composer bold', 'description': 'Set to True to make the composer bold',
'type': bool, 'type': bool,
'default': None 'default': None,
'group': 'composer'
},
'composer_is_centered': {
'description': 'Center the composer on the page',
'type': bool,
'default': None,
'group': 'composer'
}, },
'composer_is_upper': { 'composer_is_upper': {
'description': 'Force the composer to be uppercase', 'description': 'Force the composer to be uppercase',
'type': bool, 'type': bool,
'default': None, 'default': None,
'group': 'composer'
}, },
'copyright_font': { 'copyright_font': {
'description': 'The font for the copyright', 'description': 'The font for the copyright',
'type': str, 'type': str,
'default': None 'default': None,
'group': 'copyright'
}, },
'copyright_size': { 'copyright_size': {
'description': 'The size of the copyright in rem', 'description': 'The size of the copyright in rem',
'type': int, 'type': int,
'default': None 'default': None,
'group': 'copyright'
}, },
'copyright_is_bold': { 'copyright_is_bold': {
'description': 'Set to True to make the copyright bold', 'description': 'Set to True to make the copyright bold',
'type': bool, 'type': bool,
'default': False, 'default': False,
'group': 'copyright'
}, },
'copyright_is_upper': { 'copyright_is_upper': {
'description': 'Force the copyright to be uppercase', 'description': 'Force the copyright to be uppercase',
'type': bool, 'type': bool,
'default': None, 'default': None,
'group': 'copyright'
}, },
'stanza_font': { 'stanza_font': {
'description': 'The font for the stanzas', 'description': 'The font for the stanzas',
'type': str, 'type': str,
'default': None 'default': None,
'group': 'stanza'
}, },
'stanza_size': { 'stanza_size': {
'description': 'The size of the stanza in rem', 'description': 'The size of the stanza in rem',
'type': int, 'type': int,
'default': None 'default': None,
'group': 'stanza'
}, },
'stanza_is_bold': { 'stanza_is_bold': {
'description': 'Set to True to make the stanza bold', 'description': 'Set to True to make the stanza bold',
'type': bool, 'type': bool,
'default': False 'default': False,
'group': 'stanza'
}, },
'stanza_indent': { 'stanza_indent': {
'description': 'How much to indent the stanza by, in rem', 'description': 'How much to indent the stanza by, in rem',
'type': int, 'type': int,
'default': 2 'default': 4,
'group': 'stanza'
}, },
'stanza_heading_font': { 'stanza_heading_font': {
'description': 'The font for the stanza headings', 'description': 'The font for the stanza headings',
'type': str, 'type': str,
'default': None 'default': None,
'group': 'stanza_heading'
}, },
'stanza_heading_size': { 'stanza_heading_size': {
'description': 'The size of the stanza heading in rem', 'description': 'The size of the stanza heading in rem',
'type': int, 'type': int,
'default': None 'default': None,
'group': 'stanza_heading'
}, },
'stanza_heading_is_bold': { 'stanza_heading_is_bold': {
'description': 'Set to True to make the stanza heading bold', 'description': 'Set to True to make the stanza heading bold',
'type': bool, 'type': bool,
'default': True 'default': True,
'group': 'stanza_heading'
}, },
'stanza_heading_is_upper': { 'stanza_heading_is_upper': {
'description': 'Force the stanza heading to be uppercase', 'description': 'Force the stanza heading to be uppercase',
'type': bool, 'type': bool,
'default': True, 'default': True,
'group': 'stanza_heading'
}, },
'verse_font': { 'verse_font': {
'description': 'The font for the verses', 'description': 'The font for the verses',
'type': str, 'type': str,
'default': None 'default': None,
'group': 'verse'
}, },
'verse_size': { 'verse_size': {
'description': 'The size of the verse in rem', 'description': 'The size of the verse in rem',
'type': int, 'type': int,
'default': None 'default': None,
'group': 'verse'
}, },
'verse_is_bold': { 'verse_is_bold': {
'description': 'Set to True to make the verse bold', 'description': 'Set to True to make the verse bold',
'type': bool, 'type': bool,
'default': None 'default': None,
'group': 'verse'
}, },
'verse_indent': { 'verse_indent': {
'description': 'How much to indent the verse by, in rem', 'description': 'How much to indent the verse by, in rem',
'type': int, 'type': int,
'default': None 'default': None,
'group': 'verse'
}, },
'verse_heading_font': { 'verse_heading_font': {
'description': 'The font for the verse headings', 'description': 'The font for the verse headings',
'type': str, 'type': str,
'default': None 'default': None,
'group': 'verse_heading'
}, },
'verse_heading_size': { 'verse_heading_size': {
'description': 'The size of the verse heading in rem', 'description': 'The size of the verse heading in rem',
'type': int, 'type': int,
'default': None 'default': None,
'group': 'verse_heading'
}, },
'verse_heading_is_bold': { 'verse_heading_is_bold': {
'description': 'Set to True to make the verse heading bold', 'description': 'Set to True to make the verse heading bold',
'type': bool, 'type': bool,
'default': None 'default': None,
'group': 'verse_heading'
}, },
'verse_heading_is_upper': { 'verse_heading_is_upper': {
'description': 'Force the verse heading to be uppercase', 'description': 'Force the verse heading to be uppercase',
'type': bool, 'type': bool,
'default': True, 'default': True,
'group': 'verse_heading'
}, },
'chorus_font': { 'chorus_font': {
'description': 'The font for the choruses', 'description': 'The font for the choruses',
'type': str, 'type': str,
'default': None 'default': None,
'group': 'chorus'
}, },
'chorus_size': { 'chorus_size': {
'description': 'The size of the chorus in rem', 'description': 'The size of the chorus in rem',
'type': int, 'type': int,
'default': None 'default': None,
'group': 'chorus'
}, },
'chorus_is_bold': { 'chorus_is_bold': {
'description': 'Set to True to make the chorus bold', 'description': 'Set to True to make the chorus bold',
'type': bool, 'type': bool,
'default': None 'default': None,
'group': 'chorus'
}, },
'chorus_indent': { 'chorus_indent': {
'description': 'How much to indent the chorus by, in rem', 'description': 'How much to indent the chorus by, in rem',
'type': int, 'type': int,
'default': None 'default': None,
'group': 'chorus'
}, },
'chorus_heading_font': { 'chorus_heading_font': {
'description': 'The font for the chorus headings', 'description': 'The font for the chorus headings',
'type': str, 'type': str,
'default': None 'default': None,
'group': 'chorus_heading'
}, },
'chorus_heading_size': { 'chorus_heading_size': {
'description': 'The size of the chorus heading in rem', 'description': 'The size of the chorus heading in rem',
'type': int, 'type': int,
'default': None 'default': None,
'group': 'chorus_heading'
}, },
'chorus_heading_is_bold': { 'chorus_heading_is_bold': {
'description': 'Set to True to make the chorus heading bold', 'description': 'Set to True to make the chorus heading bold',
'type': bool, 'type': bool,
'default': None 'default': None,
'group': 'chorus_heading'
}, },
'chorus_heading_is_upper': { 'chorus_heading_is_upper': {
'description': 'Force the chorus heading to be uppercase', 'description': 'Force the chorus heading to be uppercase',
'type': bool, 'type': bool,
'default': True, 'default': True,
'group': 'chorus_heading'
}, },
'bridge_font': { 'bridge_font': {
'description': 'The font for the bridge', 'description': 'The font for the bridge',
'type': str, 'type': str,
'default': None 'default': None,
'group': 'bridge'
}, },
'bridge_size': { 'bridge_size': {
'description': 'The size of the bridge in rem', 'description': 'The size of the bridge in rem',
'type': int, 'type': int,
'default': None 'default': None,
'group': 'bridge'
}, },
'bridge_is_bold': { 'bridge_is_bold': {
'description': 'Set to True to make the bridge bold', 'description': 'Set to True to make the bridge bold',
'type': bool, 'type': bool,
'default': None 'default': None,
'group': 'bridge'
}, },
'bridge_indent': { 'bridge_indent': {
'description': 'How much to indent the bridge by, in rem', 'description': 'How much to indent the bridge by, in rem',
'type': int, 'type': int,
'default': None 'default': None,
'group': 'bridge'
}, },
'bridge_heading_font': { 'bridge_heading_font': {
'description': 'The font for the bridge headings', 'description': 'The font for the bridge headings',
'type': str, 'type': str,
'default': None 'default': None,
'group': 'bridge_heading'
}, },
'bridge_heading_size': { 'bridge_heading_size': {
'description': 'The size of the bridge heading in rem', 'description': 'The size of the bridge heading in rem',
'type': int, 'type': int,
'default': None 'default': None,
'group': 'bridge_heading'
}, },
'bridge_heading_is_bold': { 'bridge_heading_is_bold': {
'description': 'Set to True to make the bridge heading bold', 'description': 'Set to True to make the bridge heading bold',
'type': bool, 'type': bool,
'default': None 'default': None,
'group': 'bridge_heading'
}, },
'bridge_heading_is_upper': { 'bridge_heading_is_upper': {
'description': 'Force the bridge heading to be uppercase', 'description': 'Force the bridge heading to be uppercase',
'type': bool, 'type': bool,
'default': True, 'default': True,
'group': 'bridge_heading'
}, },
'chord_font': { 'chord_font': {
'description': 'The font for the chord', 'description': 'The font for the chord',
'type': str, 'type': str,
'default': None 'default': None,
'group': 'chord'
}, },
'chord_size': { 'chord_size': {
'description': 'The size of the chord in rem', 'description': 'The size of the chord in rem',
'type': int, 'type': int,
'default': None 'default': None,
'group': 'chord'
}, },
'chord_is_bold': { 'chord_is_bold': {
'description': 'Set to True to make the chord bold', 'description': 'Set to True to make the chord bold',
'type': bool, 'type': bool,
'default': True 'default': True,
'group': 'chord'
}, },
'chord_is_centered': { 'chord_is_centered': {
'description': 'Make chords centered over syllables', 'description': 'Make chords centered over syllables',
'type': bool, 'type': bool,
'default': True 'default': True,
'group': 'chord'
} }
} }
OPTION_GROUPS = set([option['group'] for option in HTML_OPTIONS])
def get_options(): def get_options():
@ -290,7 +353,7 @@ def get_options():
return HTML_OPTIONS return HTML_OPTIONS
def make_style(name, font=None, size=None, is_bold=None, is_centered=None, is_upper=None): def make_style(name, font=None, size=None, is_bold=None, is_centered=None, is_upper=None, indent=None):
"""Build a CSS style""" """Build a CSS style"""
styles = ['.{name} {{'.format(name=name)] styles = ['.{name} {{'.format(name=name)]
if font: if font:
@ -303,12 +366,30 @@ def make_style(name, font=None, size=None, is_bold=None, is_centered=None, is_up
styles.append(' text-align: center;') styles.append(' text-align: center;')
if is_upper: if is_upper:
styles.append(' text-transform: uppercase;') styles.append(' text-transform: uppercase;')
styles.append('}}') if indent:
styles.append(' margin-left: {indent}rem;'.format(indent=indent))
styles.append('}')
return os.linesep.join(styles) return os.linesep.join(styles)
def render(song, verse_upper=False, verse_name_bold=False, chord_bold=False): def generate_option_styles(options):
styles = []
for group in OPTION_GROUPS:
kwargs = {}
for key in STYLE_KEYS:
name = '{group}_{key}'.format(group=group, key=key)
if name not in HTML_OPTIONS:
continue
option = options.get(name, default=HTML_OPTIONS[name]['default'])
if option:
kwargs[key] = option
styles.append(make_style(group.replace('_', '-'), **kwargs))
return styles
def render(song, options=None):
"""Render a song to HTML""" """Render a song to HTML"""
styles = options and generate_option_styles(options) or []
rendered_verses = [] rendered_verses = []
for verse in song.verse_order: for verse in song.verse_order:
rendered_lines = [] rendered_lines = []
@ -328,11 +409,4 @@ def render(song, verse_upper=False, verse_name_bold=False, chord_bold=False):
title = song.metadata.get('title') or 'Song' title = song.metadata.get('title') or 'Song'
metadata = TITLE.format(title=title, artist=song.metadata.get('artist') or song.metadata.get('composer') or '') metadata = TITLE.format(title=title, artist=song.metadata.get('artist') or song.metadata.get('composer') or '')
body = metadata + '\n' + '\n'.join(rendered_verses) body = metadata + '\n' + '\n'.join(rendered_verses)
styles = '' return HTML.format(title=title, body=body, styles=os.linesep.join(styles))
if verse_name_bold:
styles += STYLES['verse_name_bold'] + '\n'
if verse_upper:
styles += STYLES['verse_upper'] + '\n'
if chord_bold:
styles += STYLES['chord_bold'] + '\n'
return HTML.format(title=title, body=body, styles=styles)