openlp/openlp/core/lib/htmlbuilder.py

545 lines
18 KiB
Python
Raw Normal View History

2010-07-11 10:58:36 +00:00
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2014 Raoul Snyman #
2014-01-14 19:25:18 +00:00
# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan #
# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
2012-11-11 21:16:14 +00:00
# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
2012-10-21 13:16:22 +00:00
# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, #
2012-11-07 21:37:01 +00:00
# Frode Woldsund, Martin Zibricky #
2010-07-11 10:58:36 +00:00
# --------------------------------------------------------------------------- #
# 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 #
###############################################################################
2010-09-04 07:48:58 +00:00
import logging
2010-10-16 13:54:57 +00:00
from PyQt4 import QtWebKit
2011-02-18 03:15:09 +00:00
from openlp.core.lib.theme import BackgroundType, BackgroundGradientType, \
VerticalType, HorizontalType
2010-10-16 13:54:57 +00:00
2010-09-04 07:48:58 +00:00
log = logging.getLogger(__name__)
2010-07-11 10:58:36 +00:00
HTMLSRC = u"""
<!DOCTYPE html>
2010-07-11 10:58:36 +00:00
<html>
<head>
<title>OpenLP Display</title>
<style>
*{
margin: 0;
2010-08-09 21:21:04 +00:00
padding: 0;
border: 0;
2010-09-04 17:55:10 +00:00
overflow: hidden;
2012-04-04 07:26:51 +00:00
-webkit-user-select: none;
2010-07-11 10:58:36 +00:00
}
2010-08-07 06:18:05 +00:00
body {
2010-09-04 07:48:58 +00:00
%s;
2010-08-07 06:18:05 +00:00
}
.size {
position: absolute;
left: 0px;
2010-08-09 21:21:04 +00:00
top: 0px;
2011-08-30 16:54:45 +00:00
width: 100%%;
height: 100%%;
2010-08-09 21:21:04 +00:00
}
#black {
z-index: 8;
background-color: black;
2010-08-09 21:21:04 +00:00
display: none;
}
#bgimage {
z-index: 1;
}
#image {
z-index: 2;
}
2011-06-08 13:18:05 +00:00
%s
2010-08-09 21:21:04 +00:00
#footer {
position: absolute;
z-index: 6;
%s
2010-08-09 21:21:04 +00:00
}
/* lyric css */
2010-07-11 10:58:36 +00:00
%s
sup {
font-size: 0.6em;
vertical-align: top;
position: relative;
top: -0.3em;
}
2010-07-11 10:58:36 +00:00
</style>
<script>
2010-08-09 21:21:04 +00:00
var timer = null;
var transition = %s;
2011-06-08 13:18:05 +00:00
%s
2010-08-07 06:18:05 +00:00
2010-08-09 21:21:04 +00:00
function show_image(src){
2010-08-07 06:18:05 +00:00
var img = document.getElementById('image');
img.src = src;
2010-08-09 21:21:04 +00:00
if(src == '')
2010-08-07 06:18:05 +00:00
img.style.display = 'none';
else
img.style.display = 'block';
}
2010-08-09 21:21:04 +00:00
function show_blank(state){
2010-08-05 05:37:26 +00:00
var black = 'none';
var lyrics = '';
switch(state){
case 'theme':
2010-08-07 06:18:05 +00:00
lyrics = 'hidden';
2010-08-05 05:37:26 +00:00
break;
case 'black':
2010-08-07 06:18:05 +00:00
black = 'block';
break;
case 'desktop':
2010-08-05 19:01:24 +00:00
break;
2010-08-05 05:37:26 +00:00
}
document.getElementById('black').style.display = black;
document.getElementById('lyricsmain').style.visibility = lyrics;
document.getElementById('image').style.visibility = lyrics;
outline = document.getElementById('lyricsoutline')
2011-12-08 19:24:25 +00:00
if(outline != null)
outline.style.visibility = lyrics;
shadow = document.getElementById('lyricsshadow')
2011-12-08 19:24:25 +00:00
if(shadow != null)
shadow.style.visibility = lyrics;
2010-08-07 06:18:05 +00:00
document.getElementById('footer').style.visibility = lyrics;
2010-08-05 05:37:26 +00:00
}
2010-08-21 10:07:59 +00:00
function show_footer(footertext){
2010-08-23 22:24:42 +00:00
document.getElementById('footer').innerHTML = footertext;
2010-08-09 21:21:04 +00:00
}
2010-08-09 21:21:04 +00:00
function show_text(newtext){
var fade_direction = 0;
var match = /-webkit-text-fill-color:[^;\"]+/gi;
2013-09-12 18:57:15 +00:00
if (timer != null)
clearInterval(timer);
2012-01-04 17:19:49 +00:00
/*
QtWebkit bug with outlines and justify causing outline alignment
problems. (Bug 859950) Surround each word with a <span> to workaround,
but only in this scenario.
2011-12-08 19:24:25 +00:00
*/
var txt = document.getElementById('lyricsmain');
if(window.getComputedStyle(txt).textAlign == 'justify'){
var outline = document.getElementById('lyricsoutline');
if(outline != null)
txt = outline;
if(window.getComputedStyle(txt).webkitTextStrokeWidth != '0px'){
newtext = newtext.replace(/(\s|&nbsp;)+(?![^<]*>)/g,
2012-01-04 17:19:49 +00:00
function(match) {
return '</span>' + match + '<span>';
});
newtext = '<span>' + newtext + '</span>';
}
}
2010-09-04 07:48:58 +00:00
text_fade('lyricsmain', newtext);
text_fade('lyricsoutline', newtext);
2011-12-08 19:24:25 +00:00
text_fade('lyricsshadow', newtext.replace(match, ''));
if(text_opacity() == 1) return;
timer = setInterval(function(){
text_fade('lyricsmain', newtext);
text_fade('lyricsoutline', newtext);
text_fade('lyricsshadow', newtext.replace(match, ''));
if(text_opacity() == 1) clearInterval(timer);
}, 100);
function text_fade(id, newtext){
/*
Using -webkit-transition: opacity 1s linear; would have been preferred
but it isn't currently quick enough when animating multiple layers of
large areas of large text. Therefore do it manually as best we can.
Hopefully in the future we can revisit and do more interesting
transitions using -webkit-transition and -webkit-transform.
However we need to ensure interrupted transitions (quickly change 2
slides) still looks pretty and is zippy.
*/
var text = document.getElementById(id);
if(text == null) return;
if(!transition){
text.innerHTML = newtext;
return;
}
if(fade_direction != 1){
text.style.opacity = parseFloat(text.style.opacity) - 0.3;
if(text.style.opacity <= 0.1){
text.innerHTML = newtext;
fade_direction = 1;
}
}else{
text.style.opacity = parseFloat(text.style.opacity) + 0.3;
if(text.style.opacity > 0.7){
text.style.opacity = 1;
}
2010-09-01 21:36:02 +00:00
}
2010-07-30 05:08:49 +00:00
}
}
2010-08-30 20:57:59 +00:00
function text_opacity(){
var text = document.getElementById('lyricsmain');
2010-09-01 21:36:02 +00:00
return getComputedStyle(text, '').opacity;
}
2010-09-04 07:48:58 +00:00
2010-08-09 21:21:04 +00:00
function show_text_complete(){
2011-12-08 19:24:25 +00:00
return (text_opacity() == 1);
2010-07-11 10:58:36 +00:00
}
</script>
</head>
<body>
<img id="bgimage" class="size" %s />
<img id="image" class="size" %s />
2011-06-08 13:18:05 +00:00
%s
%s
2010-07-19 18:03:00 +00:00
<div id="footer" class="footer"></div>
<div id="black" class="size"></div>
2010-07-11 10:58:36 +00:00
</body>
</html>
"""
2010-07-27 19:13:56 +00:00
2011-10-15 06:32:01 +00:00
def build_html(item, screen, islive, background, image=None,
2011-10-24 20:04:29 +00:00
plugins=None):
2010-07-25 08:58:08 +00:00
"""
Build the full web paged structure for display
2011-05-05 13:02:12 +00:00
``item``
2010-07-25 08:58:08 +00:00
Service Item to be displayed
2011-05-05 13:02:12 +00:00
``screen``
2010-07-25 08:58:08 +00:00
Current display information
2011-05-05 13:02:12 +00:00
``islive``
Item is going live, rather than preview/theme building
2011-05-05 13:02:12 +00:00
``background``
Theme background image - bytes
2011-05-05 13:02:12 +00:00
``image``
Image media item - bytes
2011-10-24 20:04:29 +00:00
``plugins``
The List of available plugins
2010-07-25 08:58:08 +00:00
"""
2010-07-11 10:58:36 +00:00
width = screen[u'size'].width()
height = screen[u'size'].height()
2010-07-30 05:08:49 +00:00
theme = item.themedata
webkitvers = webkit_version()
2010-10-23 07:23:49 +00:00
# Image generated and poked in
if background:
2011-04-14 21:34:01 +00:00
bgimage_src = u'src="data:image/png;base64,%s"' % background
elif item.bg_image_bytes:
2011-04-14 21:34:01 +00:00
bgimage_src = u'src="data:image/png;base64,%s"' % item.bg_image_bytes
2010-08-07 06:18:05 +00:00
else:
2011-04-14 21:34:01 +00:00
bgimage_src = u'style="display:none;"'
if image:
image_src = u'src="data:image/png;base64,%s"' % image
else:
image_src = u'style="display:none;"'
2011-08-29 19:55:58 +00:00
css_additions = u''
js_additions = u''
html_additions = u''
2011-06-08 13:18:05 +00:00
if plugins:
for plugin in plugins:
2011-08-29 19:55:58 +00:00
css_additions += plugin.getDisplayCss()
2011-08-29 21:51:03 +00:00
js_additions += plugin.getDisplayJavaScript()
2011-08-29 19:55:58 +00:00
html_additions += plugin.getDisplayHtml()
2010-09-04 07:48:58 +00:00
html = HTMLSRC % (build_background_css(item, width, height),
2011-08-29 19:55:58 +00:00
css_additions,
build_footer_css(item, height),
build_lyrics_css(item, webkitvers),
2010-09-11 06:59:36 +00:00
u'true' if theme and theme.display_slide_transition and islive \
else u'false',
2011-08-29 19:55:58 +00:00
js_additions,
2011-04-14 21:34:01 +00:00
bgimage_src, image_src,
2011-08-29 19:55:58 +00:00
html_additions,
build_lyrics_html(item, webkitvers))
2010-08-09 21:21:04 +00:00
return html
2010-07-11 10:58:36 +00:00
def webkit_version():
2010-09-04 14:23:20 +00:00
"""
Return the Webkit version in use.
Note method added relatively recently, so return 0 if prior to this
"""
try:
webkitvers = float(QtWebKit.qWebKitVersion())
2010-09-04 13:35:15 +00:00
log.debug(u'Webkit version = %s' % webkitvers)
except AttributeError:
webkitvers = 0
return webkitvers
2010-09-04 07:48:58 +00:00
def build_background_css(item, width, height):
"""
Build the background css
2011-05-05 13:02:12 +00:00
``item``
2010-09-04 07:48:58 +00:00
Service Item containing theme and location information
"""
width = int(width) / 2
theme = item.themedata
background = u'background-color: black'
if theme:
2010-12-02 14:37:38 +00:00
if theme.background_type == \
2012-01-04 17:19:49 +00:00
BackgroundType.to_string(BackgroundType.Transparent):
background = u''
elif theme.background_type == \
2010-12-02 14:37:38 +00:00
BackgroundType.to_string(BackgroundType.Solid):
2010-09-04 07:48:58 +00:00
background = u'background-color: %s' % theme.background_color
else:
2010-10-16 16:21:02 +00:00
if theme.background_direction == BackgroundGradientType.to_string \
(BackgroundGradientType.Horizontal):
2010-09-04 07:48:58 +00:00
background = \
2010-09-04 13:35:15 +00:00
u'background: ' \
u'-webkit-gradient(linear, left top, left bottom, ' \
2011-09-18 15:39:12 +00:00
'from(%s), to(%s)) fixed' % (theme.background_start_color,
2010-09-11 06:59:36 +00:00
theme.background_end_color)
2010-12-02 14:37:38 +00:00
elif theme.background_direction == \
BackgroundGradientType.to_string( \
BackgroundGradientType.LeftTop):
2010-10-16 13:54:57 +00:00
background = \
u'background: ' \
u'-webkit-gradient(linear, left top, right bottom, ' \
2011-09-18 15:39:12 +00:00
'from(%s), to(%s)) fixed' % (theme.background_start_color,
2010-10-16 13:54:57 +00:00
theme.background_end_color)
2010-12-02 14:37:38 +00:00
elif theme.background_direction == \
BackgroundGradientType.to_string \
2010-10-16 16:21:02 +00:00
(BackgroundGradientType.LeftBottom):
2010-10-16 13:54:57 +00:00
background = \
u'background: ' \
u'-webkit-gradient(linear, left bottom, right top, ' \
2011-09-18 15:39:12 +00:00
'from(%s), to(%s)) fixed' % (theme.background_start_color,
2010-10-16 13:54:57 +00:00
theme.background_end_color)
2010-12-02 14:37:38 +00:00
elif theme.background_direction == \
BackgroundGradientType.to_string \
2010-10-16 16:21:02 +00:00
(BackgroundGradientType.Vertical):
2010-09-04 07:48:58 +00:00
background = \
2010-09-04 13:35:15 +00:00
u'background: -webkit-gradient(linear, left top, ' \
2011-09-18 15:39:12 +00:00
u'right top, from(%s), to(%s)) fixed' % \
2010-09-11 06:59:36 +00:00
(theme.background_start_color, theme.background_end_color)
2010-09-04 07:48:58 +00:00
else:
background = \
2010-09-04 13:35:15 +00:00
u'background: -webkit-gradient(radial, %s 50%%, 100, %s ' \
2011-09-18 15:39:12 +00:00
u'50%%, %s, from(%s), to(%s)) fixed' % (width, width,
width, theme.background_start_color,
theme.background_end_color)
2010-09-04 07:48:58 +00:00
return background
def build_lyrics_css(item, webkitvers):
2010-07-25 08:58:08 +00:00
"""
Build the lyrics display css
2010-07-25 08:58:08 +00:00
2011-05-05 13:02:12 +00:00
``item``
2010-07-25 08:58:08 +00:00
Service Item containing theme and location information
2010-09-04 07:48:58 +00:00
2011-05-05 13:02:12 +00:00
``webkitvers``
The version of qtwebkit we're using
2010-07-25 08:58:08 +00:00
"""
style = u"""
2010-09-04 07:48:58 +00:00
.lyricstable {
z-index: 5;
2010-09-04 07:48:58 +00:00
position: absolute;
display: table;
%s
}
2010-09-04 07:48:58 +00:00
.lyricscell {
display: table-cell;
2010-09-04 07:48:58 +00:00
word-wrap: break-word;
%s
}
2010-09-04 07:48:58 +00:00
.lyricsmain {
%s
}
2010-09-04 07:48:58 +00:00
.lyricsoutline {
%s
}
2010-09-04 07:48:58 +00:00
.lyricsshadow {
%s
}
2011-04-13 09:19:16 +00:00
"""
2010-07-17 08:59:15 +00:00
theme = item.themedata
2010-07-30 05:08:49 +00:00
lyricstable = u''
2010-07-28 16:53:54 +00:00
lyrics = u''
lyricsmain = u''
outline = u''
shadow = u''
if theme and item.main:
2011-04-13 09:19:16 +00:00
lyricstable = u'left: %spx; top: %spx;' % (item.main.x(), item.main.y())
lyrics = build_lyrics_format_css(theme, item.main.width(),
item.main.height())
# For performance reasons we want to show as few DIV's as possible,
2010-09-04 07:48:58 +00:00
# especially when animating/transitions.
# However some bugs in older versions of qtwebkit mean we need to
# perform workarounds and add extra divs. Only do these when needed.
#
2010-09-04 07:48:58 +00:00
# Before 533.3 the webkit-text-fill colour wasn't displayed, only the
# stroke (outline) color. So put stroke layer underneath the main text.
#
2011-06-18 18:27:04 +00:00
# Up to 534.3 the webkit-text-stroke was sometimes out of alignment
# with the fill, or normal text. letter-spacing=1 is workaround
# https://bugs.webkit.org/show_bug.cgi?id=44403
#
2011-06-18 18:35:53 +00:00
# Up to 534.3 the text-shadow didn't get displayed when
2010-09-04 07:48:58 +00:00
# webkit-text-stroke was used. So use an offset text layer underneath.
# https://bugs.webkit.org/show_bug.cgi?id=19728
if webkitvers >= 533.3:
lyricsmain += build_lyrics_outline_css(theme)
else:
outline = build_lyrics_outline_css(theme)
if theme.font_main_shadow:
2011-06-18 18:35:53 +00:00
if theme.font_main_outline and webkitvers <= 534.3:
shadow = u'padding-left: %spx; padding-top: %spx;' % \
(int(theme.font_main_shadow_size) +
(int(theme.font_main_outline_size) * 2),
theme.font_main_shadow_size)
shadow += build_lyrics_outline_css(theme, True)
else:
lyricsmain += u' text-shadow: %s %spx %spx;' % \
2010-10-11 16:14:36 +00:00
(theme.font_main_shadow_color, theme.font_main_shadow_size,
theme.font_main_shadow_size)
lyrics_css = style % (lyricstable, lyrics, lyricsmain, outline, shadow)
return lyrics_css
def build_lyrics_outline_css(theme, is_shadow=False):
2010-09-04 14:23:20 +00:00
"""
Build the css which controls the theme outline
Also used by renderer for splitting verses
2011-05-05 13:02:12 +00:00
``theme``
2010-09-04 14:23:20 +00:00
Object containing theme information
2011-05-05 13:02:12 +00:00
``is_shadow``
2010-09-04 14:23:20 +00:00
If true, use the shadow colors instead
"""
if theme.font_main_outline:
size = float(theme.font_main_outline_size) / 16
if is_shadow:
fill_color = theme.font_main_shadow_color
outline_color = theme.font_main_shadow_color
else:
fill_color = theme.font_main_color
outline_color = theme.font_main_outline_color
return u' -webkit-text-stroke: %sem %s; ' \
u'-webkit-text-fill-color: %s; ' % (size, outline_color, fill_color)
else:
return u''
def build_lyrics_format_css(theme, width, height):
"""
Build the css which controls the theme format
Also used by renderer for splitting verses
2011-05-05 13:02:12 +00:00
``theme``
2010-09-04 14:23:20 +00:00
Object containing theme information
2011-05-05 13:02:12 +00:00
``width``
2010-09-04 14:23:20 +00:00
Width of the lyrics block
2011-05-05 13:02:12 +00:00
``height``
2010-09-04 14:23:20 +00:00
Height of the lyrics block
"""
2011-02-18 03:15:09 +00:00
align = HorizontalType.Names[theme.display_horizontal_align]
valign = VerticalType.Names[theme.display_vertical_align]
if theme.font_main_outline:
left_margin = int(theme.font_main_outline_size) * 2
else:
left_margin = 0
justify = u'white-space:pre-wrap;'
# fix tag incompatibilities
if theme.display_horizontal_align == HorizontalType.Justify:
justify = u''
2012-03-21 22:27:04 +00:00
if theme.display_vertical_align == VerticalType.Bottom:
padding_bottom = u'0.5em'
else:
padding_bottom = u'0'
lyrics = u'%s word-wrap: break-word; ' \
'text-align: %s; vertical-align: %s; font-family: %s; ' \
'font-size: %spt; color: %s; line-height: %d%%; margin: 0;' \
2012-03-21 22:27:04 +00:00
'padding: 0; padding-bottom: %s; padding-left: %spx; width: %spx;' \
'height: %spx; ' % \
(justify, align, valign, theme.font_main_name, theme.font_main_size,
theme.font_main_color, 100 + int(theme.font_main_line_adjustment),
2012-03-21 22:27:04 +00:00
padding_bottom, left_margin, width, height)
if theme.font_main_outline:
2011-06-18 18:27:04 +00:00
if webkit_version() <= 534.3:
lyrics += u' letter-spacing: 1px;'
if theme.font_main_italics:
lyrics += u' font-style:italic; '
if theme.font_main_bold:
lyrics += u' font-weight:bold; '
return lyrics
def build_lyrics_html(item, webkitvers):
"""
Build the HTML required to show the lyrics
2010-07-19 18:03:00 +00:00
2011-05-05 13:02:12 +00:00
``item``
Service Item containing theme and location information
2010-09-04 07:48:58 +00:00
2011-05-05 13:02:12 +00:00
``webkitvers``
The version of qtwebkit we're using
"""
2010-09-04 07:48:58 +00:00
# Bugs in some versions of QtWebKit mean we sometimes need additional
# divs for outline and shadow, since the CSS doesn't work.
# To support vertical alignment middle and bottom, nested div's using
# display:table/display:table-cell are required for each lyric block.
lyrics = u''
theme = item.themedata
2011-06-18 18:35:53 +00:00
if webkitvers <= 534.3 and theme and theme.font_main_outline:
lyrics += u'<div class="lyricstable">' \
2010-09-03 18:30:56 +00:00
u'<div id="lyricsshadow" style="opacity:1" ' \
u'class="lyricscell lyricsshadow"></div></div>'
if webkitvers < 533.3:
lyrics += u'<div class="lyricstable">' \
2010-09-03 18:30:56 +00:00
u'<div id="lyricsoutline" style="opacity:1" ' \
u'class="lyricscell lyricsoutline"></div></div>'
lyrics += u'<div class="lyricstable">' \
2010-09-03 18:30:56 +00:00
u'<div id="lyricsmain" style="opacity:1" ' \
u'class="lyricscell lyricsmain"></div></div>'
return lyrics
2010-09-04 07:48:58 +00:00
def build_footer_css(item, height):
2010-08-03 04:56:21 +00:00
"""
Build the display of the item footer
2011-05-05 13:02:12 +00:00
``item``
2010-08-03 04:56:21 +00:00
Service Item to be processed.
"""
style = u"""
2010-08-30 20:57:59 +00:00
left: %spx;
bottom: %spx;
2010-08-30 20:57:59 +00:00
width: %spx;
font-family: %s;
font-size: %spt;
color: %s;
text-align: left;
white-space: nowrap;
2010-07-19 18:03:00 +00:00
"""
theme = item.themedata
if not theme or not item.footer:
2010-08-09 21:21:04 +00:00
return u''
bottom = height - int(item.footer.y()) - int(item.footer.height())
lyrics_html = style % (item.footer.x(), bottom,
2010-09-09 07:30:14 +00:00
item.footer.width(), theme.font_footer_name,
2010-10-11 16:14:36 +00:00
theme.font_footer_size, theme.font_footer_color)
2010-07-12 16:49:38 +00:00
return lyrics_html