# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2017 OpenLP Developers #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
This module is responsible for generating the HTML for :class:`~openlp.core.ui.maindisplay`. The ``build_html`` function
is the function which has to be called from outside. The generated and returned HTML will look similar to this::
OpenLP Display
"""
import logging
from string import Template
from PyQt5 import QtWebKit
from openlp.core.common.settings import Settings
from openlp.core.lib.theme import BackgroundType, BackgroundGradientType, VerticalType, HorizontalType
log = logging.getLogger(__name__)
HTML_SRC = Template("""
OpenLP Display
${html_additions}
""")
LYRICS_SRC = Template("""
.lyricstable {
z-index: 5;
position: absolute;
display: table;
${stable}
}
.lyricscell {
display: table-cell;
word-wrap: break-word;
-webkit-transition: opacity 0.4s ease;
${lyrics}
}
.lyricsmain {
${main}
}
""")
FOOTER_SRC = Template("""
left: ${left}px;
bottom: ${bottom}px;
width: ${width}px;
font-family: ${family};
font-size: ${size}pt;
color: ${color};
text-align: left;
white-space: ${space};
""")
LYRICS_FORMAT_SRC = Template("""
${justify}word-wrap: break-word;
text-align: ${align};
vertical-align: ${valign};
font-family: ${font};
font-size: ${size}pt;
color: ${color};
line-height: ${line}%;
margin: 0;
padding: 0;
padding-bottom: ${bottom};
padding-left: ${left}px;
width: ${width}px;
height: ${height}px;${font_style}${font_weight}
""")
CHORDS_FORMAT = Template("""
.chordline {
line-height: ${chord_line_height};
}
.chordline span.chord span {
position: relative;
}
.chordline span.chord span strong {
position: absolute;
top: -0.8em;
left: 0;
font-size: 75%;
font-weight: normal;
line-height: normal;
display: ${chords_display};
}
.firstchordline {
line-height: ${first_chord_line_height};
}
.ws {
display: ${chords_display};
white-space: pre-wrap;
}""")
def build_html(item, screen, is_live, background, image=None, plugins=None):
"""
Build the full web paged structure for display
:param item: Service Item to be displayed
:param screen: Current display information
:param is_live: Item is going live, rather than preview/theme building
:param background: Theme background image - bytes
:param image: Image media item - bytes
:param plugins: The List of available plugins
"""
width = screen['size'].width()
height = screen['size'].height()
theme_data = item.theme_data
# Image generated and poked in
if background:
bgimage_src = 'src="data:image/png;base64,{image}"'.format(image=background)
elif item.bg_image_bytes:
bgimage_src = 'src="data:image/png;base64,{image}"'.format(image=item.bg_image_bytes)
else:
bgimage_src = 'style="display:none;"'
if image:
image_src = 'src="data:image/png;base64,{image}"'.format(image=image)
else:
image_src = 'style="display:none;"'
css_additions = ''
js_additions = ''
html_additions = ''
if plugins:
for plugin in plugins:
css_additions += plugin.get_display_css()
js_additions += plugin.get_display_javascript()
html_additions += plugin.get_display_html()
return HTML_SRC.substitute(bg_css=build_background_css(item, width),
css_additions=css_additions,
footer_css=build_footer_css(item, height),
lyrics_css=build_lyrics_css(item),
transitions='true' if (theme_data and
theme_data.display_slide_transition and
is_live) else 'false',
js_additions=js_additions,
bg_image=bgimage_src,
image=image_src,
html_additions=html_additions,
chords_css=build_chords_css())
def webkit_version():
"""
Return the Webkit version in use. Note method added relatively recently, so return 0 if prior to this
"""
try:
webkit_ver = float(QtWebKit.qWebKitVersion())
log.debug('Webkit version = {version}'.format(version=webkit_ver))
except AttributeError:
webkit_ver = 0.0
return webkit_ver
def build_background_css(item, width):
"""
Build the background css
:param item: Service Item containing theme and location information
:param width:
"""
width = int(width) // 2
theme = item.theme_data
background = 'background-color: black'
if theme:
if theme.background_type == BackgroundType.to_string(BackgroundType.Transparent):
background = ''
elif theme.background_type == BackgroundType.to_string(BackgroundType.Solid):
background = 'background-color: {theme}'.format(theme=theme.background_color)
else:
if theme.background_direction == BackgroundGradientType.to_string(BackgroundGradientType.Horizontal):
background = 'background: -webkit-gradient(linear, left top, left bottom, from({start}), to({end})) ' \
'fixed'.format(start=theme.background_start_color, end=theme.background_end_color)
elif theme.background_direction == BackgroundGradientType.to_string(BackgroundGradientType.LeftTop):
background = 'background: -webkit-gradient(linear, left top, right bottom, from({start}), to({end})) ' \
'fixed'.format(start=theme.background_start_color, end=theme.background_end_color)
elif theme.background_direction == BackgroundGradientType.to_string(BackgroundGradientType.LeftBottom):
background = 'background: -webkit-gradient(linear, left bottom, right top, from({start}), to({end})) ' \
'fixed'.format(start=theme.background_start_color, end=theme.background_end_color)
elif theme.background_direction == BackgroundGradientType.to_string(BackgroundGradientType.Vertical):
background = 'background: -webkit-gradient(linear, left top, right top, from({start}), to({end})) ' \
'fixed'.format(start=theme.background_start_color, end=theme.background_end_color)
else:
background = 'background: -webkit-gradient(radial, {width} 50%, 100, {width} 50%, {width}, ' \
'from({start}), to({end})) fixed'.format(width=width,
start=theme.background_start_color,
end=theme.background_end_color)
return background
def build_lyrics_css(item):
"""
Build the lyrics display css
:param item: Service Item containing theme and location information
"""
theme_data = item.theme_data
lyricstable = ''
lyrics = ''
lyricsmain = ''
if theme_data and item.main:
lyricstable = 'left: {left}px; top: {top}px;'.format(left=item.main.x(), top=item.main.y())
lyrics = build_lyrics_format_css(theme_data, item.main.width(), item.main.height())
lyricsmain += build_lyrics_outline_css(theme_data)
if theme_data.font_main_shadow:
lyricsmain += ' text-shadow: {theme} {shadow}px ' \
'{shadow}px;'.format(theme=theme_data.font_main_shadow_color,
shadow=theme_data.font_main_shadow_size)
return LYRICS_SRC.substitute(stable=lyricstable, lyrics=lyrics, main=lyricsmain)
def build_lyrics_outline_css(theme_data):
"""
Build the css which controls the theme outline. Also used by renderer for splitting verses
:param theme_data: Object containing theme information
"""
if theme_data.font_main_outline:
size = float(theme_data.font_main_outline_size) / 16
fill_color = theme_data.font_main_color
outline_color = theme_data.font_main_outline_color
return ' -webkit-text-stroke: {size}em {color}; -webkit-text-fill-color: {fill}; '.format(size=size,
color=outline_color,
fill=fill_color)
return ''
def build_lyrics_format_css(theme_data, width, height):
"""
Build the css which controls the theme format. Also used by renderer for splitting verses
:param theme_data: Object containing theme information
:param width: Width of the lyrics block
:param height: Height of the lyrics block
"""
align = HorizontalType.Names[theme_data.display_horizontal_align]
valign = VerticalType.Names[theme_data.display_vertical_align]
left_margin = (int(theme_data.font_main_outline_size) * 2) if theme_data.font_main_outline else 0
# fix tag incompatibilities
justify = '' if (theme_data.display_horizontal_align == HorizontalType.Justify) else ' white-space: pre-wrap;\n'
padding_bottom = '0.5em' if (theme_data.display_vertical_align == VerticalType.Bottom) else '0'
return LYRICS_FORMAT_SRC.substitute(justify=justify,
align=align,
valign=valign,
font=theme_data.font_main_name,
size=theme_data.font_main_size,
color=theme_data.font_main_color,
line='{line:d}'.format(line=100 + int(theme_data.font_main_line_adjustment)),
bottom=padding_bottom,
left=left_margin,
width=width,
height=height,
font_style='\n font-style: italic;' if theme_data.font_main_italics else '',
font_weight='\n font-weight: bold;' if theme_data.font_main_bold else '')
def build_footer_css(item, height):
"""
Build the display of the item footer
:param item: Service Item to be processed.
:param height:
"""
theme = item.theme_data
if not theme or not item.footer:
return ''
bottom = height - int(item.footer.y()) - int(item.footer.height())
whitespace = 'normal' if Settings().value('themes/wrap footer') else 'nowrap'
return FOOTER_SRC.substitute(left=item.footer.x(), bottom=bottom, width=item.footer.width(),
family=theme.font_footer_name, size=theme.font_footer_size,
color=theme.font_footer_color, space=whitespace)
def build_chords_css():
if Settings().value('songs/enable chords') and Settings().value('songs/mainview chords'):
chord_line_height = '2.0em'
chords_display = 'inline'
first_chord_line_height = '2.1em'
else:
chord_line_height = '1.0em'
chords_display = 'none'
first_chord_line_height = '1.0em'
return CHORDS_FORMAT.substitute(chord_line_height=chord_line_height, chords_display=chords_display,
first_chord_line_height=first_chord_line_height)