1
# -*- coding: utf-8 -*-
2
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
4
###############################################################################
5
# OpenLP - Open Source Lyrics Projection #
6
# --------------------------------------------------------------------------- #
7
# Copyright (c) 2008-2018 OpenLP Developers #
8
# --------------------------------------------------------------------------- #
9
# This program is free software; you can redistribute it and/or modify it #
10
# under the terms of the GNU General Public License as published by the Free #
11
# Software Foundation; version 2 of the License. #
13
# This program is distributed in the hope that it will be useful, but WITHOUT #
14
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
15
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
18
# You should have received a copy of the GNU General Public License along #
19
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
20
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
21
###############################################################################
23
This module is responsible for generating the HTML for :class:`~openlp.core.ui.maindisplay`. The ``build_html`` function
24
is the function which has to be called from outside. The generated and returned HTML will look similar to this::
29
<title>OpenLP Display</title>
36
-webkit-user-select: none;
39
background-color: #000000;
50
background-color: black;
62
background-color: #000000;
65
background-color: #000000;
79
vertical-align: bottom;
80
font-family: DejaVu Sans;
83
background-color: #660000;
84
word-wrap: break-word;
94
font-family: Nimbus Sans L;
107
left: 10px; top: 0px;
111
word-wrap: break-word;
112
-webkit-transition: opacity 0.4s ease;
113
white-space:pre-wrap; word-wrap: break-word; text-align: left; vertical-align: top; font-family: Nimbus
114
Sans L; font-size: 40pt; color: #FFFFFF; line-height: 100%; margin: 0;padding: 0; padding-bottom: 0;
115
padding-left: 4px; width: 1580px; height: 810px;
118
-webkit-text-stroke: 0.125em #000000; -webkit-text-fill-color: #FFFFFF; text-shadow: #000000 5px 5px;
131
.chordline span.chord span {
134
.chordline span.chord span strong {
149
var transition = false;
151
function show_video(state, path, volume, loop, variable_value){
152
// Sometimes video.currentTime stops slightly short of video.duration and video.ended is intermittent!
154
var video = document.getElementById('video');
156
video.volume = volume;
160
video.src = 'file:///' + path;
174
video.currentTime = 0;
181
return video.duration;
183
return video.currentTime;
185
video.currentTime = variable_value;
190
video.style.visibility = variable_value;
193
var back = document.getElementById('videobackboard');
194
back.style.visibility = variable_value;
199
function getFlashMovieObject(movieName)
201
if (window.document[movieName]){
202
return window.document[movieName];
204
if (document.embeds && document.embeds[movieName]){
205
return document.embeds[movieName];
209
function show_flash(state, path, volume, variable_value){
210
var text = document.getElementById('flash');
211
var flashMovie = getFlashMovieObject("OpenLPFlashMovie");
212
var src = "src = 'file:///" + path + "'";
213
var view_parm = " wmode='opaque'" + " width='100%%'" + " height='100%%'";
214
var swf_parm = " name='OpenLPFlashMovie'" + " autostart='true' loop='false' play='true'" +
215
" hidden='false' swliveconnect='true' allowscriptaccess='always'" + " volume='" + volume + "'";
219
text.innerHTML = "<embed " + src + view_parm + swf_parm + "/>";
220
flashMovie = getFlashMovieObject("OpenLPFlashMovie");
227
flashMovie.StopPlay();
230
flashMovie.StopPlay();
231
tempHtml = text.innerHTML;
233
text.innerHTML = tempHtml;
236
flashMovie.StopPlay();
240
return flashMovie.TotalFrames();
242
return flashMovie.CurrentFrame();
244
// flashMovie.GotoFrame(variable_value);
247
//TODO check flash end
250
text.style.visibility = variable_value;
255
function show_alert(alerttext, position){
256
var text = document.getElementById('alert');
257
text.innerHTML = alerttext;
258
if(alerttext == '') {
259
text.style.visibility = 'hidden';
263
position = getComputedStyle(text, '').verticalAlign;
268
text.style.top = '0px';
271
text.style.top = ((window.innerHeight - text.clientHeight) / 2)
275
text.style.top = (window.innerHeight - text.clientHeight)
279
text.style.visibility = 'visible';
280
return text.clientHeight;
283
function update_css(align, font, size, color, bgcolor){
284
var text = document.getElementById('alert');
285
text.style.fontSize = size + "pt";
286
text.style.fontFamily = font;
287
text.style.color = color;
288
text.style.backgroundColor = bgcolor;
292
text.style.top = '0px';
295
text.style.top = ((window.innerHeight - text.clientHeight) / 2)
299
text.style.top = (window.innerHeight - text.clientHeight)
306
function show_image(src){
307
var img = document.getElementById('image');
310
img.style.display = 'none';
312
img.style.display = 'block';
315
function show_blank(state){
328
document.getElementById('black').style.display = black;
329
document.getElementById('lyricsmain').style.visibility = lyrics;
330
document.getElementById('image').style.visibility = lyrics;
331
document.getElementById('footer').style.visibility = lyrics;
334
function show_footer(footertext){
335
document.getElementById('footer').innerHTML = footertext;
338
function show_text(new_text){
339
var match = /-webkit-text-fill-color:[^;"]+/gi;
343
QtWebkit bug with outlines and justify causing outline alignment
344
problems. (Bug 859950) Surround each word with a <span> to workaround,
345
but only in this scenario.
347
var txt = document.getElementById('lyricsmain');
348
if(window.getComputedStyle(txt).textAlign == 'justify'){
349
if(window.getComputedStyle(txt).webkitTextStrokeWidth != '0px'){
350
new_text = new_text.replace(/(\s| )+(?![^<]*>)/g,
352
return '</span>' + match + '<span>';
354
new_text = '<span>' + new_text + '</span>';
357
text_fade('lyricsmain', new_text);
360
function text_fade(id, new_text){
364
var text = document.getElementById(id);
365
if(text == null) return;
367
text.innerHTML = new_text;
370
// Fade text out. 0.1 to minimize the time "nothing" is shown on the screen.
371
text.style.opacity = '0.1';
372
// Fade new text in after the old text has finished fading out.
373
timer = window.setTimeout(function(){_show_text(text, new_text)}, 400);
376
function _show_text(text, new_text) {
378
Helper function to show the new_text delayed.
380
text.innerHTML = new_text;
381
text.style.opacity = '1';
382
// Wait until the text is completely visible. We want to save the timer id, to be able to call
383
// clearTimeout(timer) when the text has changed before finishing fading.
384
timer = window.setTimeout(function(){timer = null;}, 400);
387
function show_text_completed(){
388
return (timer == null);
393
<img id="bgimage" class="size" style="display:none;" />
394
<img id="image" class="size" style="display:none;" />
396
<div id="videobackboard" class="size" style="visibility:hidden"></div>
397
<video id="video" class="size" style="visibility:hidden" autobuffer preload></video>
399
<div id="flash" class="size" style="visibility:hidden"></div>
401
<div id="alert" style="visibility:hidden"></div>
403
<div class="lyricstable"><div id="lyricsmain" style="opacity:1" class="lyricscell lyricsmain"></div></div>
404
<div id="footer" class="footer"></div>
405
<div id="black" class="size"></div>
410
from string import Template
412
from PyQt5 import QtWebKit
414
from openlp.core.common.settings import Settings
415
from openlp.core.lib.theme import BackgroundType, BackgroundGradientType, VerticalType, HorizontalType
417
log = logging.getLogger(__name__)
419
HTML_SRC = Template(r"""
423
<title>OpenLP Display</title>
430
-webkit-user-select: none;
444
background-color: black;
459
/* lyric css */${lyrics_css}
466
/* Chords css */${chords_css}
470
var transition = ${transitions};
473
function show_image(src){
474
var img = document.getElementById('image');
477
img.style.display = 'none';
479
img.style.display = 'block';
482
function show_blank(state){
495
document.getElementById('black').style.display = black;
496
document.getElementById('lyricsmain').style.visibility = lyrics;
497
document.getElementById('image').style.visibility = lyrics;
498
document.getElementById('footer').style.visibility = lyrics;
501
function show_footer(footertext){
502
document.getElementById('footer').innerHTML = footertext;
505
function show_text(new_text){
506
var match = /-webkit-text-fill-color:[^;\"]+/gi;
510
QtWebkit bug with outlines and justify causing outline alignment
511
problems. (Bug 859950) Surround each word with a <span> to workaround,
512
but only in this scenario.
514
var txt = document.getElementById('lyricsmain');
515
if(window.getComputedStyle(txt).textAlign == 'justify'){
516
if(window.getComputedStyle(txt).webkitTextStrokeWidth != '0px'){
517
new_text = new_text.replace(/(\s| )+(?![^<]*>)/g,
519
return '</span>' + match + '<span>';
521
new_text = '<span>' + new_text + '</span>';
524
text_fade('lyricsmain', new_text);
527
function text_fade(id, new_text){
531
var text = document.getElementById(id);
532
if(text == null) return;
534
text.innerHTML = new_text;
537
// Fade text out. 0.1 to minimize the time "nothing" is shown on the screen.
538
text.style.opacity = '0.1';
539
// Fade new text in after the old text has finished fading out.
540
timer = window.setTimeout(function(){_show_text(text, new_text)}, 400);
543
function _show_text(text, new_text) {
545
Helper function to show the new_text delayed.
547
text.innerHTML = new_text;
548
text.style.opacity = '1';
549
// Wait until the text is completely visible. We want to save the timer id, to be able to call
550
// clearTimeout(timer) when the text has changed before finishing fading.
551
timer = window.setTimeout(function(){timer = null;}, 400);
554
function show_text_completed(){
555
return (timer == null);
560
<img id="bgimage" class="size" ${bg_image} />
561
<img id="image" class="size" ${image} />
563
<div class="lyricstable"><div id="lyricsmain" style="opacity:1" class="lyricscell lyricsmain"></div></div>
564
<div id="footer" class="footer"></div>
565
<div id="black" class="size"></div>
570
LYRICS_SRC = Template("""
579
word-wrap: break-word;
580
-webkit-transition: opacity 0.4s ease;
588
FOOTER_SRC = Template("""
592
font-family: ${family};
593
font-size: ${size}pt;
596
white-space: ${space};
599
LYRICS_FORMAT_SRC = Template("""
600
${justify}word-wrap: break-word;
601
text-align: ${align};
602
vertical-align: ${valign};
603
font-family: ${font};
604
font-size: ${size}pt;
606
line-height: ${line}%;
609
padding-bottom: ${bottom};
610
padding-left: ${left}px;
612
height: ${height}px;${font_style}${font_weight}
615
CHORDS_FORMAT = Template("""
617
line-height: ${chord_line_height};
619
.chordline span.chord span {
622
.chordline span.chord span strong {
629
display: ${chords_display};
632
line-height: ${first_chord_line_height};
635
display: ${chords_display};
636
white-space: pre-wrap;
640
def build_html(item, screen, is_live, background, image=None, plugins=None):
642
Build the full web paged structure for display
644
:param item: Service Item to be displayed
645
:param screen: Current display information
646
:param is_live: Item is going live, rather than preview/theme building
647
:param background: Theme background image - bytes
648
:param image: Image media item - bytes
649
:param plugins: The List of available plugins
651
width = screen['size'].width()
652
height = screen['size'].height()
653
theme_data = item.theme_data
654
# Image generated and poked in
656
bgimage_src = 'src="data:image/png;base64,{image}"'.format(image=background)
657
elif item.bg_image_bytes:
658
bgimage_src = 'src="data:image/png;base64,{image}"'.format(image=item.bg_image_bytes)
660
bgimage_src = 'style="display:none;"'
662
image_src = 'src="data:image/png;base64,{image}"'.format(image=image)
664
image_src = 'style="display:none;"'
669
for plugin in plugins:
670
css_additions += plugin.get_display_css()
671
js_additions += plugin.get_display_javascript()
672
html_additions += plugin.get_display_html()
673
return HTML_SRC.substitute(bg_css=build_background_css(item, width),
674
css_additions=css_additions,
675
footer_css=build_footer_css(item, height),
676
lyrics_css=build_lyrics_css(item),
677
transitions='true' if (theme_data and
678
theme_data.display_slide_transition and
679
is_live) else 'false',
680
js_additions=js_additions,
681
bg_image=bgimage_src,
683
html_additions=html_additions,
684
chords_css=build_chords_css())
687
def webkit_version():
689
Return the Webkit version in use. Note method added relatively recently, so return 0 if prior to this
692
webkit_ver = float(QtWebKit.qWebKitVersion())
693
log.debug('Webkit version = {version}'.format(version=webkit_ver))
694
except AttributeError:
699
def build_background_css(item, width):
701
Build the background css
703
:param item: Service Item containing theme and location information
706
width = int(width) // 2
707
theme = item.theme_data
708
background = 'background-color: black'
710
if theme.background_type == BackgroundType.to_string(BackgroundType.Transparent):
712
elif theme.background_type == BackgroundType.to_string(BackgroundType.Solid):
713
background = 'background-color: {theme}'.format(theme=theme.background_color)
715
if theme.background_direction == BackgroundGradientType.to_string(BackgroundGradientType.Horizontal):
716
background = 'background: -webkit-gradient(linear, left top, left bottom, from({start}), to({end})) ' \
717
'fixed'.format(start=theme.background_start_color, end=theme.background_end_color)
718
elif theme.background_direction == BackgroundGradientType.to_string(BackgroundGradientType.LeftTop):
719
background = 'background: -webkit-gradient(linear, left top, right bottom, from({start}), to({end})) ' \
720
'fixed'.format(start=theme.background_start_color, end=theme.background_end_color)
721
elif theme.background_direction == BackgroundGradientType.to_string(BackgroundGradientType.LeftBottom):
722
background = 'background: -webkit-gradient(linear, left bottom, right top, from({start}), to({end})) ' \
723
'fixed'.format(start=theme.background_start_color, end=theme.background_end_color)
724
elif theme.background_direction == BackgroundGradientType.to_string(BackgroundGradientType.Vertical):
725
background = 'background: -webkit-gradient(linear, left top, right top, from({start}), to({end})) ' \
726
'fixed'.format(start=theme.background_start_color, end=theme.background_end_color)
728
background = 'background: -webkit-gradient(radial, {width} 50%, 100, {width} 50%, {width}, ' \
729
'from({start}), to({end})) fixed'.format(width=width,
730
start=theme.background_start_color,
731
end=theme.background_end_color)
735
def build_lyrics_css(item):
737
Build the lyrics display css
739
:param item: Service Item containing theme and location information
741
theme_data = item.theme_data
745
if theme_data and item.main:
746
lyricstable = 'left: {left}px; top: {top}px;'.format(left=item.main.x(), top=item.main.y())
747
lyrics = build_lyrics_format_css(theme_data, item.main.width(), item.main.height())
748
lyricsmain += build_lyrics_outline_css(theme_data)
749
if theme_data.font_main_shadow:
750
lyricsmain += ' text-shadow: {theme} {shadow}px ' \
751
'{shadow}px;'.format(theme=theme_data.font_main_shadow_color,
752
shadow=theme_data.font_main_shadow_size)
753
return LYRICS_SRC.substitute(stable=lyricstable, lyrics=lyrics, main=lyricsmain)
756
def build_lyrics_outline_css(theme_data):
758
Build the css which controls the theme outline. Also used by renderer for splitting verses
760
:param theme_data: Object containing theme information
762
if theme_data.font_main_outline:
763
size = float(theme_data.font_main_outline_size) / 16
764
fill_color = theme_data.font_main_color
765
outline_color = theme_data.font_main_outline_color
766
return ' -webkit-text-stroke: {size}em {color}; -webkit-text-fill-color: {fill}; '.format(size=size,
772
def build_lyrics_format_css(theme_data, width, height):
774
Build the css which controls the theme format. Also used by renderer for splitting verses
776
:param theme_data: Object containing theme information
777
:param width: Width of the lyrics block
778
:param height: Height of the lyrics block
780
align = HorizontalType.Names[theme_data.display_horizontal_align]
781
valign = VerticalType.Names[theme_data.display_vertical_align]
782
left_margin = (int(theme_data.font_main_outline_size) * 2) if theme_data.font_main_outline else 0
783
# fix tag incompatibilities
784
justify = '' if (theme_data.display_horizontal_align == HorizontalType.Justify) else ' white-space: pre-wrap;\n'
785
padding_bottom = '0.5em' if (theme_data.display_vertical_align == VerticalType.Bottom) else '0'
786
return LYRICS_FORMAT_SRC.substitute(justify=justify,
789
font=theme_data.font_main_name,
790
size=theme_data.font_main_size,
791
color=theme_data.font_main_color,
792
line='{line:d}'.format(line=100 + int(theme_data.font_main_line_adjustment)),
793
bottom=padding_bottom,
797
font_style='\n font-style: italic;' if theme_data.font_main_italics else '',
798
font_weight='\n font-weight: bold;' if theme_data.font_main_bold else '')
801
def build_footer_css(item, height):
803
Build the display of the item footer
805
:param item: Service Item to be processed.
808
theme = item.theme_data
809
if not theme or not item.footer:
811
bottom = height - int(item.footer.y()) - int(item.footer.height())
812
whitespace = 'normal' if Settings().value('themes/wrap footer') else 'nowrap'
813
return FOOTER_SRC.substitute(left=item.footer.x(), bottom=bottom, width=item.footer.width(),
814
family=theme.font_footer_name, size=theme.font_footer_size,
815
color=theme.font_footer_color, space=whitespace)
818
def build_chords_css():
819
if Settings().value('songs/enable chords') and Settings().value('songs/mainview chords'):
820
chord_line_height = '2.0em'
821
chords_display = 'inline'
822
first_chord_line_height = '2.1em'
824
chord_line_height = '1.0em'
825
chords_display = 'none'
826
first_chord_line_height = '1.0em'
827
return CHORDS_FORMAT.substitute(chord_line_height=chord_line_height, chords_display=chords_display,
828
first_chord_line_height=first_chord_line_height)