# -*- 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)