There's a single page demo of lyrics over a backgroung image, with the theme
settings coming from an XML file. The renderer should perform all the tasks required by the current (1.0.x) theme capabilities, and there are plenty of testcases for the code. The code itself is still WIP, so there's lots of debug print()s in there and some nastiness to be tidied up. bzr-revno: 13
64
demo.py
Normal file
@ -0,0 +1,64 @@
|
||||
from openlp.core import Renderer
|
||||
from openlp.theme import Theme
|
||||
import sys
|
||||
import time
|
||||
|
||||
from PyQt4 import QtGui, QtCore
|
||||
words="""How sweet the name of Jesus sounds
|
||||
In a believer's ear!
|
||||
It soothes his sorrows, heals his wounds,
|
||||
And drives away his fear.
|
||||
"""
|
||||
|
||||
class TstFrame(QtGui.QMainWindow):
|
||||
""" We simply derive a new class of QMainWindow"""
|
||||
|
||||
# {{{ init
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""Create the DemoPanel."""
|
||||
QtGui.QMainWindow.__init__(self)
|
||||
self.resize(1024,768)
|
||||
self.size=(1024,768)
|
||||
|
||||
self.v=0
|
||||
self._font=QtGui.QFont('Decorative', 32)
|
||||
self.framecount=0
|
||||
self.totaltime = 0
|
||||
self.dir=1
|
||||
self.y=1
|
||||
# self.startTimer(10)
|
||||
self.frame=QtGui.QFrame()
|
||||
self.setCentralWidget(self.frame)
|
||||
self.r=Renderer()
|
||||
self.r.set_theme(Theme('demo_theme.xml'))
|
||||
|
||||
self.r.set_text_rectangle(self.frame.frameRect())
|
||||
self.r.set_paint_dest(self)
|
||||
self.r.set_words_openlp(words)
|
||||
def timerEvent(self, event):
|
||||
self.update()
|
||||
def paintEvent(self, event):
|
||||
self.r.set_text_rectangle(self.frame.frameRect())
|
||||
self.r.scale_bg_image()
|
||||
t1=time.clock()
|
||||
self.r.render_screen(0)
|
||||
t2 = time.clock()
|
||||
deltat=t2-t1
|
||||
self.totaltime += deltat
|
||||
self.framecount+=1
|
||||
print "Timing result: %5.3ffps" %(self.framecount/float(self.totaltime))
|
||||
|
||||
# }}}
|
||||
|
||||
class Demo:
|
||||
def __init__(self):
|
||||
app = QtGui.QApplication(sys.argv)
|
||||
main=TstFrame()
|
||||
main.show()
|
||||
sys.exit(app.exec_())
|
||||
|
||||
|
||||
if __name__=="__main__":
|
||||
t=Demo()
|
||||
|
17
demo_theme.xml
Normal file
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<Theme>
|
||||
<Name>openlp.org 2.0 Demo Theme</Name>
|
||||
<BackgroundType>2</BackgroundType>
|
||||
<BackgroundParameter1>./openlp/core/test/data_for_tests/treesbig.jpg</BackgroundParameter1>
|
||||
<BackgroundParameter2>clBlack</BackgroundParameter2>
|
||||
<BackgroundParameter3/>
|
||||
<FontName>Tahoma</FontName>
|
||||
<FontColor>clWhite</FontColor>
|
||||
<FontProportion>16</FontProportion>
|
||||
<Shadow>-1</Shadow>
|
||||
<ShadowColor>$00000001</ShadowColor>
|
||||
<Outline>-1</Outline>
|
||||
<OutlineColor>clRed</OutlineColor>
|
||||
<HorizontalAlign>2</HorizontalAlign>
|
||||
<VerticalAlign>2</VerticalAlign>
|
||||
</Theme>
|
1
openlp/core/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from openlp.core.render import Renderer
|
21
openlp/core/interpolate.py
Normal file
@ -0,0 +1,21 @@
|
||||
# useful linear interpolation routines
|
||||
|
||||
def interp1(val1, val2, fraction):
|
||||
"""return a linear 1d interpolation between val1 and val2 by fraction
|
||||
if fraction=0.0, returns val1
|
||||
if fraction=1.0, returns val2"""
|
||||
return val1+((val2-val1)*fraction)
|
||||
def interpolate(val1, val2, fraction):
|
||||
"vals can be list/tuples - if so, will return a tuple of interpolated values for each element."
|
||||
assert (fraction >= 0.0)
|
||||
assert (fraction <= 1.0)
|
||||
assert (type(val1) == type(val2))
|
||||
if (type(val1) == type(()) or
|
||||
type (val1) == type([])):
|
||||
assert(len(val1) == len(val2))
|
||||
retval=[]
|
||||
for i in range(len(val1)):
|
||||
retval.append(interp1(val1[i], val2[i], fraction))
|
||||
return tuple(retval)
|
||||
else:
|
||||
return interp1(val1, val2, fraction)
|
383
openlp/core/render.py
Normal file
@ -0,0 +1,383 @@
|
||||
import sys
|
||||
from PyQt4 import QtGui, QtCore, Qt
|
||||
|
||||
from copy import copy
|
||||
from interpolate import interpolate
|
||||
class Renderer:
|
||||
"""All the functions for rendering a set of words onto a Device Context
|
||||
|
||||
How to use:
|
||||
set the words to be displayed with a call to set_words_openlp() - this returns an array of screenfuls of data
|
||||
set a theme (if you need) with set_theme
|
||||
tell it which DC to render to with set_DC()
|
||||
set the borders of where you want the text (if not the whole DC) with set_text_rectangle()
|
||||
tell it to render a particular screenfull with render_screen(n)
|
||||
|
||||
"""
|
||||
def __init__(self):
|
||||
self._rect=None
|
||||
self._debug=0
|
||||
self.words=None
|
||||
self._right_margin = 64 # the amount of right indent
|
||||
self._shadow_offset=5
|
||||
self._outline_offset=2
|
||||
self._theme=None
|
||||
self._bg_image_filename=None
|
||||
self._paint=None
|
||||
def set_debug(self, debug):
|
||||
self._debug=debug
|
||||
def set_theme(self, theme):
|
||||
self._theme=theme
|
||||
if theme.BackgroundType == 2:
|
||||
self.set_bg_image(theme.BackgroundParameter1)
|
||||
|
||||
def set_bg_image(self, filename):
|
||||
print "set bg image", filename
|
||||
self._bg_image_filename=filename
|
||||
if self._paint is not None:
|
||||
self.scale_bg_image()
|
||||
def scale_bg_image(self):
|
||||
assert self._paint
|
||||
i=QtGui.QImage(self._bg_image_filename)
|
||||
# rescale and offset
|
||||
imw=i.width();imh=i.height()
|
||||
dcw=self._paint.width()+1;dch=self._paint.height()
|
||||
imratio=imw/float(imh)
|
||||
dcratio=dcw/float(dch)
|
||||
print "Image scaling params", imw, imh, imratio, dcw, dch, dcratio
|
||||
if imratio > dcratio:
|
||||
scale=dcw/float(imw)
|
||||
elif imratio < dcratio:
|
||||
scale=dch/float(imh)
|
||||
else:
|
||||
scale=dcw/float(imw) # either will do
|
||||
neww=int(round(imw*scale))
|
||||
newh=int(round(imh*scale))
|
||||
self.background_offsetx=(dcw-neww)/2
|
||||
self.background_offsety=(dch-newh)/2
|
||||
self.img=QtGui.QPixmap.fromImage(i.scaled(QtCore.QSize(neww, newh), Qt.Qt.KeepAspectRatio))
|
||||
|
||||
def set_paint_dest(self, p):
|
||||
self._paint=p
|
||||
if self._bg_image_filename is not None:
|
||||
self.scale_bg_image()
|
||||
def set_words_openlp(self, words):
|
||||
# print "set words openlp", words
|
||||
verses=[]
|
||||
words=words.replace("\r\n", "\n")
|
||||
verses_text=words.split('\n\n')
|
||||
for v in verses_text:
|
||||
lines=v.split('\n')
|
||||
verses.append(self.split_set_of_lines(lines)[0])
|
||||
self.words=verses
|
||||
verses_text=[]
|
||||
for v in verses:
|
||||
verses_text.append('\n'.join(v).lstrip()) # remove first \n
|
||||
|
||||
return verses_text
|
||||
def render_screen(self, screennum):
|
||||
print "render screen\n", screennum, self.words[screennum]
|
||||
import time
|
||||
t=0.0
|
||||
words=self.words[screennum]
|
||||
retval=self._render_lines(words)
|
||||
return retval
|
||||
|
||||
def set_text_rectangle(self, rect):
|
||||
""" Sets the rectangle within which text should be rendered"""
|
||||
self._rect=rect
|
||||
def _render_background(self):
|
||||
# xxx may have to prerender to a memdc when set theme is called for use on slow machines
|
||||
# takes 26ms on mijiti's machine!
|
||||
assert(self._theme)
|
||||
assert(self._paint)
|
||||
print "render background", self._theme.BackgroundType
|
||||
p=QtGui.QPainter()
|
||||
p.begin(self._paint)
|
||||
if self._theme.BackgroundType == 0:
|
||||
p.fillRect(self._paint.rect(), self._theme.BackgroundParameter1)
|
||||
elif self._theme.BackgroundType == 1: # gradient
|
||||
# get colours as tuples
|
||||
c1=self._theme.BackgroundParameter1.getRgb()
|
||||
c2=self._theme.BackgroundParameter2.getRgb()
|
||||
dir=self._theme.BackgroundParameter3
|
||||
w=self._paint.width();h=self._paint.height()
|
||||
lines=[]
|
||||
pens=[]
|
||||
if dir == 0: # vertical
|
||||
for y in range (h):
|
||||
c=interpolate(c1, c2, y/float(h))
|
||||
lines.append((0,y,w,y))
|
||||
pens.append(QtGui.QPen(QtGui.QColor(c[0],c[1],c[2]))) # bleagh
|
||||
else:
|
||||
for x in range (w):
|
||||
c=interpolate(c1, c2, x/float(w))
|
||||
lines.append((x,0,x,h))
|
||||
pens.append(QtGui.QPen(QtGui.QColor(c[0],c[1],c[2]))) # bleagh
|
||||
for i in range(len(pens)):
|
||||
p.setPen(pens[i])
|
||||
l=lines[i]
|
||||
p.drawLine(l[0],l[1],l[2],l[3]) # must be a better way!
|
||||
|
||||
elif self._theme.BackgroundType == 2: # image
|
||||
r=self._paint.rect()
|
||||
print r.x(), r.y(), r.width(),r.height()
|
||||
print self._theme.BackgroundParameter2
|
||||
if self._theme.BackgroundParameter2 is not None:
|
||||
p.fillRect(self._paint.rect(), self._theme.BackgroundParameter2)
|
||||
p.drawPixmap(self.background_offsetx,self.background_offsety, self.img)
|
||||
p.end()
|
||||
print "render background done"
|
||||
def split_set_of_lines(self, lines):
|
||||
|
||||
"""Given a list of lines, decide how to split them best if they don't all fit on the screen
|
||||
- this is done by splitting at 1/2, 1/3 or 1/4 of the set
|
||||
If it doesn't fit, even at this size, just split at each opportunity
|
||||
|
||||
We'll do this by getting the bounding box of each line, and then summing them appropriately
|
||||
|
||||
Returns a list of [lists of lines], one set for each screenful
|
||||
"""
|
||||
# print "Split set of lines"
|
||||
# Probably ought to save the rendering results to a pseudoDC for redrawing efficiency. But let's not optimse prematurely!
|
||||
|
||||
bboxes = []
|
||||
for line in lines:
|
||||
bboxes.append(self._render_single_line(line))
|
||||
numlines=len(lines)
|
||||
bottom=self._rect.bottom()
|
||||
for ratio in (numlines, numlines/2, numlines/3, numlines/4):
|
||||
good=1
|
||||
startline=0
|
||||
endline=startline+ratio
|
||||
while (endline<=numlines):
|
||||
by=0
|
||||
for (x,y) in bboxes[startline:endline]:
|
||||
by+=y
|
||||
if by > bottom:
|
||||
good=0
|
||||
break
|
||||
startline+=ratio
|
||||
endline=startline+ratio
|
||||
if good==1:
|
||||
break
|
||||
|
||||
retval=[]
|
||||
numlines_per_page=ratio
|
||||
if good:
|
||||
c=0
|
||||
thislines=[]
|
||||
while c < numlines:
|
||||
thislines.append(lines[c])
|
||||
c+=1
|
||||
if len(thislines) == numlines_per_page:
|
||||
retval.append(thislines)
|
||||
thislines=[]
|
||||
else:
|
||||
# print "Just split where you can"
|
||||
retval=[]
|
||||
startline=0
|
||||
endline=startline+1
|
||||
while (endline<=numlines):
|
||||
by=0
|
||||
for (x,y) in bboxes[startline:endline]:
|
||||
by+=y
|
||||
if by > bottom:
|
||||
retval.append(lines[startline:endline-1])
|
||||
startline=endline-1
|
||||
endline=startline # gets incremented below
|
||||
by=0
|
||||
endline+=1
|
||||
|
||||
return retval
|
||||
|
||||
|
||||
def _render_lines(self, lines):
|
||||
"""render a set of lines according to the theme, return bounding box"""
|
||||
print "_render_lines", lines
|
||||
|
||||
bbox=self._render_lines_unaligned(lines)
|
||||
|
||||
t=self._theme
|
||||
x=self._rect.left()
|
||||
if t.VerticalAlign==0: # top align
|
||||
y = self._rect.top()
|
||||
elif t.VerticalAlign==1: # bottom align
|
||||
y=self._rect.bottom()-bbox.height()
|
||||
elif t.VerticalAlign==2: # centre align
|
||||
y=self._rect.top()+(self._rect.height()-bbox.height())/2
|
||||
else:
|
||||
assert(0, "Invalid value for theme.VerticalAlign:%d" % t.VerticalAlign)
|
||||
self._render_background()
|
||||
bbox=self._render_lines_unaligned(lines, (x,y))
|
||||
print "render lines DONE"
|
||||
|
||||
return bbox
|
||||
def _render_lines_unaligned(self, lines, tlcorner=(0,0)):
|
||||
|
||||
"""Given a list of lines to render, render each one in turn
|
||||
(using the _render_single_line fn - which may result in going
|
||||
off the bottom) They are expected to be pre-arranged to less
|
||||
than a screenful (eg. by using split_set_of_lines)
|
||||
|
||||
Returns the bounding box of the text as QRect"""
|
||||
print "render unaligned", lines
|
||||
x,y=tlcorner
|
||||
brx=x
|
||||
bry=y
|
||||
for line in lines:
|
||||
if (line == ''):
|
||||
continue
|
||||
# render after current bottom, but at original left edge
|
||||
# keep track of right edge to see which is biggest
|
||||
(thisx, bry) = self._render_single_line(line, (x,bry))
|
||||
if (thisx > brx):
|
||||
brx=thisx
|
||||
retval=QtCore.QRect(x,y,brx-x, bry-y)
|
||||
if self._debug:
|
||||
p=QtGui.QPainter()
|
||||
p.begin(self._paint)
|
||||
p.setPen(QtGui.QPen(QtGui.QColor(0,0,255)))
|
||||
p.drawRect(retval)
|
||||
p.end()
|
||||
print "render unaligned DONE"
|
||||
|
||||
return retval
|
||||
|
||||
|
||||
def _render_single_line(self, line, tlcorner=(0,0)):
|
||||
|
||||
"""render a single line of words onto the DC, top left corner
|
||||
specified.
|
||||
|
||||
If the line is too wide for the context, it wraps, but
|
||||
right-aligns the surplus words in the manner of song lyrics
|
||||
|
||||
Returns the bottom-right corner (of what was rendered) as a tuple(x,y).
|
||||
"""
|
||||
print "Render single line '%s' @ %s "%( line, tlcorner)
|
||||
x,y=tlcorner
|
||||
# We draw the text to see how big it is and then iterate to make it fit
|
||||
# when we line wrap we do in in the "lyrics" style, so the second line is
|
||||
# right aligned with a "hanging indent"
|
||||
|
||||
# get the words
|
||||
# print "Getting the words split right"
|
||||
words=line.split(" ")
|
||||
thisline=' '.join(words)
|
||||
lastword=len(words)
|
||||
lines=[]
|
||||
maxx=self._rect.width(); maxy=self._rect.height();
|
||||
while (len(words)>0):
|
||||
w,h=self._get_extent_and_render(thisline)
|
||||
rhs=w+x
|
||||
if rhs < maxx-self._right_margin:
|
||||
lines.append(thisline)
|
||||
words=words[lastword:]
|
||||
thisline=' '.join(words)
|
||||
lastword=len(words)
|
||||
else:
|
||||
lastword-=1
|
||||
thisline=' '.join(words[:lastword])
|
||||
|
||||
# print "This is how they split", lines
|
||||
# print "Now render them"
|
||||
startx=x
|
||||
starty=y
|
||||
rightextent=None
|
||||
t=self._theme
|
||||
align=t.HorizontalAlign
|
||||
wrapstyle=t.WrapStyle
|
||||
|
||||
for linenum in range(len(lines)):
|
||||
line=lines[linenum]
|
||||
#find out how wide line is
|
||||
w,h=self._get_extent_and_render(line, tlcorner=(x,y), dodraw=False)
|
||||
|
||||
if t.Shadow:
|
||||
w+=self._shadow_offset
|
||||
h+=self._shadow_offset
|
||||
if t.Outline:
|
||||
w+=2*self._outline_offset # pixels either side
|
||||
h+=2*self._outline_offset # pixels top/bottom
|
||||
if align==0: # left align
|
||||
rightextent=x+w
|
||||
if wrapstyle==1 and linenum != 0: # shift right from last line's rh edge
|
||||
rightextent=self._first_line_right_extent + self._right_margin
|
||||
if rightextent > maxx:
|
||||
rightextent = maxx
|
||||
x = rightextent-w
|
||||
|
||||
elif align==1: # right align
|
||||
rightextent=maxx
|
||||
x=maxx-w
|
||||
elif align==2: # centre
|
||||
x=(maxx-w)/2;
|
||||
rightextent=x+w
|
||||
# now draw the text, and any outlines/shadows
|
||||
if t.Shadow:
|
||||
self._get_extent_and_render(line, tlcorner=(x+self._shadow_offset,y+self._shadow_offset), dodraw=True, color = t.ShadowColor)
|
||||
if t.Outline:
|
||||
self._get_extent_and_render(line, (x+self._outline_offset,y), dodraw=True, color = t.OutlineColor)
|
||||
self._get_extent_and_render(line, (x,y+self._outline_offset), dodraw=True, color = t.OutlineColor)
|
||||
self._get_extent_and_render(line, (x,y-self._outline_offset), dodraw=True, color = t.OutlineColor)
|
||||
self._get_extent_and_render(line, (x-self._outline_offset,y), dodraw=True, color = t.OutlineColor)
|
||||
if self._outline_offset > 1:
|
||||
self._get_extent_and_render(line, (x+self._outline_offset,y+self._outline_offset), dodraw=True, color = t.OutlineColor)
|
||||
self._get_extent_and_render(line, (x-self._outline_offset,y+self._outline_offset), dodraw=True, color = t.OutlineColor)
|
||||
self._get_extent_and_render(line, (x+self._outline_offset,y-self._outline_offset), dodraw=True, color = t.OutlineColor)
|
||||
self._get_extent_and_render(line, (x-self._outline_offset,y-self._outline_offset), dodraw=True, color = t.OutlineColor)
|
||||
|
||||
self._get_extent_and_render(line, tlcorner=(x,y), dodraw=True)
|
||||
# print "Line %2d: Render '%s' at (%d, %d) wh=(%d,%d)"%( linenum, line, x, y,w,h)
|
||||
y += h
|
||||
if linenum == 0:
|
||||
self._first_line_right_extent=rightextent
|
||||
# draw a box around the text - debug only
|
||||
if self._debug:
|
||||
p=QtGui.QPainter()
|
||||
p.begin(self._paint)
|
||||
p.setPen(QtGui.QPen(QtGui.QColor(0,255,0)))
|
||||
p.drawRect(startx,starty,rightextent-startx,y-starty)
|
||||
p.end()
|
||||
|
||||
brcorner=(rightextent,y)
|
||||
return brcorner
|
||||
|
||||
# xxx this is what to override for an SDL version
|
||||
def _get_extent_and_render(self, line, tlcorner=(0,0), dodraw=False, color=None):
|
||||
"""Find bounding box of text - as render_single_line.
|
||||
If dodraw is set, actually draw the text to the current DC as well
|
||||
|
||||
return width and height of text as a tuple (w,h)"""
|
||||
# setup defaults
|
||||
print "_get_extent_and_render", [line], tlcorner, dodraw
|
||||
p=QtGui.QPainter()
|
||||
p.begin(self._paint)
|
||||
# use this to scale for rendering in "operators view" xxx
|
||||
# p.SetUserScale(0.5,0.5)
|
||||
# 'twould be more efficient to set this once when theme changes
|
||||
# or p changes
|
||||
font=QtGui.QFont(self._theme.FontName,
|
||||
self._theme.FontProportion, # size
|
||||
QtGui.QFont.Normal, # weight
|
||||
0)# italic
|
||||
p.setFont(font)
|
||||
if color == None:
|
||||
p.setPen(self._theme.FontColor)
|
||||
else:
|
||||
p.setPen(color)
|
||||
x,y=tlcorner
|
||||
metrics=QtGui.QFontMetrics(font)
|
||||
# xxx some fudges to make it exactly like wx! Take 'em out later
|
||||
w=metrics.width(line)
|
||||
h=metrics.height()-2
|
||||
if dodraw:
|
||||
p.drawText(x,y+metrics.height()-metrics.descent()-1, line)
|
||||
p.end()
|
||||
return (w, h)
|
||||
|
||||
|
||||
|
||||
|
||||
|
18
openlp/core/test/data_for_tests/render_theme.xml
Normal file
@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<Theme>
|
||||
<Name>openlp.org Packaged Theme</Name>
|
||||
<BackgroundType>0</BackgroundType>
|
||||
<BackgroundParameter1>clWhite</BackgroundParameter1>
|
||||
<BackgroundParameter2/>
|
||||
<BackgroundParameter3/>
|
||||
<FontName>Tahoma</FontName>
|
||||
<FontColor>$00007F</FontColor>
|
||||
<FontProportion>32</FontProportion>
|
||||
<Shadow>0</Shadow>
|
||||
<ShadowColor>$000000</ShadowColor>
|
||||
<Outline>0</Outline>
|
||||
<OutlineColor>$000000</OutlineColor>
|
||||
<HorizontalAlign>0</HorizontalAlign>
|
||||
<VerticalAlign>0</VerticalAlign>
|
||||
<WrapStyle>1</WrapStyle>
|
||||
</Theme>
|
BIN
openlp/core/test/data_for_tests/snowbig.jpg
Normal file
After Width: | Height: | Size: 197 KiB |
BIN
openlp/core/test/data_for_tests/snowsmall.jpg
Normal file
After Width: | Height: | Size: 26 KiB |
BIN
openlp/core/test/data_for_tests/sunset1.jpg
Normal file
After Width: | Height: | Size: 194 KiB |
BIN
openlp/core/test/data_for_tests/treesbig.jpg
Normal file
After Width: | Height: | Size: 517 KiB |
BIN
openlp/core/test/data_for_tests/treessmall.jpg
Normal file
After Width: | Height: | Size: 67 KiB |
BIN
openlp/core/test/golden_bitmaps/test_bg_shrink_x.bmp
Normal file
After Width: | Height: | Size: 1.4 MiB |
BIN
openlp/core/test/golden_bitmaps/test_bg_shrink_y.bmp
Normal file
After Width: | Height: | Size: 1.4 MiB |
BIN
openlp/core/test/golden_bitmaps/test_bg_stretch_x.bmp
Normal file
After Width: | Height: | Size: 1.4 MiB |
BIN
openlp/core/test/golden_bitmaps/test_bg_stretch_y.bmp
Normal file
After Width: | Height: | Size: 1.4 MiB |
BIN
openlp/core/test/golden_bitmaps/test_gradient_h.bmp
Normal file
After Width: | Height: | Size: 1.4 MiB |
BIN
openlp/core/test/golden_bitmaps/test_gradient_v.bmp
Normal file
After Width: | Height: | Size: 1.4 MiB |
BIN
openlp/core/test/golden_bitmaps/test_theme_basic.bmp
Normal file
After Width: | Height: | Size: 1.4 MiB |
BIN
openlp/core/test/golden_bitmaps/test_theme_font.bmp
Normal file
After Width: | Height: | Size: 1.4 MiB |
After Width: | Height: | Size: 1.4 MiB |
After Width: | Height: | Size: 1.4 MiB |
After Width: | Height: | Size: 1.4 MiB |
After Width: | Height: | Size: 1.4 MiB |
BIN
openlp/core/test/golden_bitmaps/test_theme_shadow_outline.bmp
Normal file
After Width: | Height: | Size: 1.4 MiB |
After Width: | Height: | Size: 1.4 MiB |
After Width: | Height: | Size: 1.4 MiB |
After Width: | Height: | Size: 1.4 MiB |
206
openlp/core/test/test_render.py
Normal file
@ -0,0 +1,206 @@
|
||||
import time
|
||||
import sys
|
||||
import os, os.path
|
||||
from PyQt4 import QtGui, QtCore
|
||||
|
||||
mypath=os.path.split(os.path.abspath(__file__))[0]
|
||||
sys.path.insert(0,(os.path.join(mypath, '..', '..','..')))
|
||||
from openlp.theme import Theme
|
||||
from openlp.core import Renderer
|
||||
# from http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/66062
|
||||
def whoami(depth=1):
|
||||
return sys._getframe(depth).f_code.co_name
|
||||
|
||||
class TstFrame:
|
||||
# {{{ init
|
||||
|
||||
def __init__(self, size):
|
||||
"""Create the DemoPanel."""
|
||||
self.width=size.width();
|
||||
self.height=size.height();
|
||||
# create something to be painted into
|
||||
self._Buffer = QtGui.QPixmap(self.width, self.height)
|
||||
def GetPixmap(self):
|
||||
return self._Buffer
|
||||
|
||||
# }}}
|
||||
|
||||
class TestRender_base:
|
||||
def __init__(self):
|
||||
if not os.path.exists("test_results"):
|
||||
os.mkdir("test_results")
|
||||
self.app=None
|
||||
def write_to_file(self, pixmap, name):
|
||||
im=pixmap.toImage()
|
||||
testpathname=os.path.join("test_results", name+".bmp")
|
||||
if os.path.exists(testpathname):
|
||||
os.unlink(testpathname)
|
||||
im.save(testpathname, "bmp")
|
||||
return im
|
||||
# xxx quitting the app still leaves it hanging aroudn so we die
|
||||
# when trying to start another one. Not quitting doesn't help
|
||||
# though This means that the py.test runs both test modules in
|
||||
# sequence and the second one tries to create another application
|
||||
# which gives us errors :(
|
||||
|
||||
def setup_class(self):
|
||||
print "class setup", self
|
||||
try:
|
||||
if self.app is None:
|
||||
pass
|
||||
except AttributeError: # didn't have one
|
||||
print "No app"
|
||||
self.app = None
|
||||
|
||||
print "Test app (should be None)"
|
||||
if self.app is None:
|
||||
print "App is None"
|
||||
self.app = QtGui.QApplication([])
|
||||
else:
|
||||
print "class setup, app is", app
|
||||
# self.app = QtGui.QApplication([])
|
||||
|
||||
def teardown_class(self):
|
||||
print "class quit", self, self.app
|
||||
self.app.quit()
|
||||
# def setup_module(self):
|
||||
# print "Module setup"
|
||||
# self.app = QtGui.QApplication([])
|
||||
# def teardown_module(self):
|
||||
# print "Module quit"
|
||||
# self.app.quit()
|
||||
def setup_method(self, method):
|
||||
print "SSsetup", method
|
||||
if not hasattr(self, "app"):
|
||||
self.app=None
|
||||
try: # see if we already have an app for some reason.
|
||||
# have to try and so something, cant just test against None
|
||||
print "app", self.app, ";;;"
|
||||
print self.app.quit()
|
||||
print "quitted"
|
||||
except RuntimeError: # not valid app, create one
|
||||
print "Runtime error"
|
||||
except AttributeError: # didn't have one
|
||||
print "Attribute error"
|
||||
# print "App", self.app
|
||||
# self.app = QtGui.QApplication([])
|
||||
print "Application created and sorted"
|
||||
self.size=QtCore.QSize(800,600)
|
||||
frame=TstFrame(size=self.size)
|
||||
self.frame=frame
|
||||
self.paintdest=frame.GetPixmap()
|
||||
self.r=Renderer()
|
||||
self.r.set_paint_dest(self.paintdest)
|
||||
self.expected_answer="Don't know yet"
|
||||
self.answer=None
|
||||
print "--------------- Setup Done -------------"
|
||||
|
||||
def teardown_method(self, method):
|
||||
self.write_to_file(self.frame.GetPixmap(), "test_render")
|
||||
|
||||
class TestRender(TestRender_base):
|
||||
def __init__(self):
|
||||
TestRender_base.__init__(self)
|
||||
|
||||
def setup_method(self, method):
|
||||
TestRender_base.setup_method(self, method)
|
||||
self.r.set_debug(1)
|
||||
themefile=os.path.abspath("data_for_tests/render_theme.xml")
|
||||
self.r.set_theme(Theme(themefile)) # set default theme
|
||||
self.r._render_background()
|
||||
self.r.set_text_rectangle(QtCore.QRect(0,0, self.size.width()-1, self.size.height()-1))
|
||||
self.msg=None
|
||||
|
||||
def test_easy(self):
|
||||
answer=self.r._render_single_line("Test line", tlcorner=(0,100))
|
||||
assert (answer==(219,163))
|
||||
def test_longer(self):
|
||||
answer=self.r._render_single_line("Test line with more words than fit on one line",
|
||||
tlcorner=(10,10))
|
||||
assert (answer==(753,136))
|
||||
def test_even_longer(self):
|
||||
answer=self.r._render_single_line("Test line with more words than fit on either one or two lines",
|
||||
tlcorner=(10,10))
|
||||
assert(answer==(753,199))
|
||||
def test_lines(self):
|
||||
lines=[]
|
||||
lines.append("Line One")
|
||||
lines.append("Line Two")
|
||||
lines.append("Line Three and should be long enough to wrap")
|
||||
lines.append("Line Four and should be long enough to wrap also")
|
||||
answer=self.r._render_lines(lines)
|
||||
assert(answer==QtCore.QRect(0,0,741,378))
|
||||
|
||||
def test_set_words_openlp(self):
|
||||
words="""
|
||||
Verse 1: Line 1
|
||||
Line 2
|
||||
|
||||
Verse 2: Line 1
|
||||
Line 2
|
||||
|
||||
Verse 3: Line 1
|
||||
Line 2
|
||||
Line 3"""
|
||||
expected_answer=["Verse 1: Line 1\nLine 2","Verse 2: Line 1\nLine 2","Verse 3: Line 1\nLine 2\nLine 3"]
|
||||
answer=self.r.set_words_openlp(words)
|
||||
assert (answer==expected_answer)
|
||||
def test_render_screens(self):
|
||||
words="""
|
||||
Verse 1: Line 1
|
||||
Line 2
|
||||
|
||||
Verse 2: Line 1
|
||||
Line 2
|
||||
|
||||
Verse 3: Line 1
|
||||
Line 2
|
||||
Line 3"""
|
||||
verses=self.r.set_words_openlp(words)
|
||||
expected_answer=["Verse 1: Line 1\nLine 2","Verse 2: Line 1\nLine 2","Verse 3: Line 1\nLine 2\nLine 3"]
|
||||
assert (verses==expected_answer)
|
||||
|
||||
expected_answer=[QtCore.QRect(0,0,397,126), QtCore.QRect(0,0,397,126), QtCore.QRect(0,0,397,189)]
|
||||
for v in range(len(verses)):
|
||||
answer=self.r.render_screen(v)
|
||||
# print v, answer.x(), answer.y(), answer.width(), answer.height()
|
||||
assert(answer==expected_answer[v])
|
||||
def split_test(self, number, answer, expected_answers):
|
||||
lines=[]
|
||||
print "Split test", number, answer
|
||||
for i in range(number):
|
||||
extra=""
|
||||
if i == 51: # make an extra long line on line 51 to test wrapping
|
||||
extra="Some more words to make it wrap around don't you know until it wraps so many times we don't know what to do"
|
||||
lines.append("Line %d %s" % (i, extra))
|
||||
result=self.r.split_set_of_lines(lines)
|
||||
print "results---------------__", result
|
||||
for i in range(len(result)):
|
||||
self.setup_method(None)
|
||||
answer=self.r._render_lines(result[i])
|
||||
print answer
|
||||
self.write_to_file(self.frame.GetPixmap(), "split_test_%03d"% i)
|
||||
print number, i, answer.x(), answer.y(), answer.width(), answer.height()
|
||||
|
||||
e=expected_answers[i]
|
||||
assert(answer==QtCore.QRect(e[0],e[1],e[2],e[3]))
|
||||
|
||||
|
||||
def test_splits(self):
|
||||
print "Test splits"
|
||||
self.split_test(100, 11, [(0,0,180,567), (0,0,214,567), (0,0,214,567), (0,0,214,567), (0,0,214,567), (0,0,214,378), (0,0,759,567),
|
||||
(0,0,214,567), (0,0,214,567), (0,0,214,567), (0,0,214,567), (0,0,214,567), (0,0,214,567)])
|
||||
self.split_test(30, 4, [ (0,0,180,441), (0,0,214,441), (0,0,214,441), (0,0,214,441)])
|
||||
self.split_test(20, 3, [(0,0,180,378), (0,0,214,378), (0,0,214,378)])
|
||||
self.split_test(12, 2, [(0,0,180,378), (0,0,214,378)])
|
||||
self.split_test(4, 1, [(0,0,180,252)])
|
||||
self.split_test(6, 1, [(0,0,180,378)])
|
||||
self.split_test(8, 1, [(0,0,180,504)])
|
||||
if __name__=="__main__":
|
||||
|
||||
t=TestRender()
|
||||
t.setup_class()
|
||||
t.setup_method(None)
|
||||
t.test_splits()
|
||||
t.teardown_method(None)
|
||||
|
260
openlp/core/test/test_render_theme.py
Normal file
@ -0,0 +1,260 @@
|
||||
from test_render import TestRender_base, whoami
|
||||
import sys
|
||||
import os
|
||||
mypath=os.path.split(os.path.abspath(__file__))[0]
|
||||
sys.path.insert(0,(os.path.join(mypath, '..', '..','..')))
|
||||
from openlp.theme import Theme
|
||||
from openlp.core import Renderer
|
||||
|
||||
from PyQt4 import QtGui, QtCore
|
||||
class TestRenderTheme(TestRender_base):
|
||||
# {{{ Basics
|
||||
|
||||
def __init__(self):
|
||||
TestRender_base.__init__(self)
|
||||
def setup_method(self, method):
|
||||
TestRender_base.setup_method(self, method)
|
||||
print "Theme setup", method
|
||||
# print "setup theme"
|
||||
self.r.set_theme(Theme()) # set "blank" theme
|
||||
self.r.set_text_rectangle(QtCore.QRect(0,0, self.size.width(), self.size.height()))
|
||||
words="""How sweet the name of Jesus sounds
|
||||
In a believer's ear!
|
||||
It soothes his sorrows, heals his wounds,
|
||||
And drives away his fear.
|
||||
"""
|
||||
verses=self.r.set_words_openlp(words)
|
||||
# usually the same
|
||||
self.expected_answer= QtCore.QRect(0, 0, 559, 342)
|
||||
self.msg=None
|
||||
self.bmpname="Not set a bitmap yet"
|
||||
print "------------- setup done --------------"
|
||||
|
||||
def teardown_method(self, method):
|
||||
print "============ teardown =============", method, self.bmpname
|
||||
if self.bmpname != None:
|
||||
assert (self.compare_DC_to_file(self.bmpname))
|
||||
if self.expected_answer != None: # result=None => No result to check
|
||||
assert self.expected_answer==self.answer
|
||||
print "============ teardown done ========="
|
||||
def compare_DC_to_file(self, name):
|
||||
"""writes DC out to a bitmap file and then compares it with a golden one
|
||||
returns True if OK, False if not (so you can assert on it)
|
||||
|
||||
"""
|
||||
print "--- compare DC to file --- ", name
|
||||
p=self.frame.GetPixmap()
|
||||
im=self.write_to_file(p, name)
|
||||
print "Compare"
|
||||
goldenfilename=os.path.join("golden_bitmaps",name+".bmp")
|
||||
if os.path.exists(goldenfilename):
|
||||
goldenim=QtGui.QImage(goldenfilename)
|
||||
else:
|
||||
print "File", goldenfilename, "not found"
|
||||
return False
|
||||
if (goldenim == im):
|
||||
print name, "Images match"
|
||||
return True
|
||||
else:
|
||||
print name, goldenfilename, "Images don't match"
|
||||
return False
|
||||
|
||||
def test_theme_basic(self):
|
||||
self.answer=self.r.render_screen(0)
|
||||
self.bmpname=whoami()
|
||||
print self.answer, self.expected_answer, self.answer==self.expected_answer
|
||||
# self.msg=self.bmpname
|
||||
|
||||
# }}}
|
||||
# {{{ Gradients
|
||||
def test_gradient_h(self):
|
||||
# normally we wouldn't hack with these directly!
|
||||
self.r._theme.BackgroundType = 1
|
||||
self.r._theme.BackgroundParameter1 = QtGui.QColor(255,0,0);
|
||||
self.r._theme.BackgroundParameter2 = QtGui.QColor(255,255,0);
|
||||
self.r._theme.BackgroundParameter3 = 1
|
||||
self.answer=self.r.render_screen(0)
|
||||
self.bmpname=whoami()
|
||||
|
||||
def test_gradient_v(self):
|
||||
# normally we wouldn't hack with these directly!
|
||||
self.r._theme.BackgroundType = 1
|
||||
self.r._theme.BackgroundParameter1 = QtGui.QColor(255,0,0);
|
||||
self.r._theme.BackgroundParameter2 = QtGui.QColor(255,255,0);
|
||||
self.r._theme.BackgroundParameter3 = 0
|
||||
self.answer=self.r.render_screen(0)
|
||||
self.bmpname=whoami()
|
||||
# }}}
|
||||
# {{{ backgrounds
|
||||
def test_bg_stretch_y(self):
|
||||
t=Theme()
|
||||
t.BackgroundType = 2
|
||||
t.BackgroundParameter1 = os.path.join('data_for_tests', "snowsmall.jpg");
|
||||
t.BackgroundParameter2 = QtGui.QColor(0,0,64);
|
||||
t.BackgroundParameter3 = 0
|
||||
t.Name="stretch y"
|
||||
print t
|
||||
print "set theme"
|
||||
self.r.set_theme(t)
|
||||
print "render"
|
||||
self.answer=self.r.render_screen(0)
|
||||
print "whoami"
|
||||
self.bmpname=whoami()
|
||||
print "fone"
|
||||
def test_bg_shrink_y(self):
|
||||
t=Theme()
|
||||
t.BackgroundType = 2
|
||||
t.BackgroundParameter1 = os.path.join('data_for_tests', "snowbig.jpg");
|
||||
t.BackgroundParameter2 = QtGui.QColor(0,0,64);
|
||||
t.BackgroundParameter3 = 0
|
||||
t.Name="shrink y"
|
||||
self.r.set_theme(t)
|
||||
self.answer=self.r.render_screen(0)
|
||||
self.bmpname=whoami()
|
||||
|
||||
def test_bg_stretch_x(self):
|
||||
t=Theme()
|
||||
t.BackgroundType = 2
|
||||
t.BackgroundParameter1 = os.path.join('data_for_tests', "treessmall.jpg");
|
||||
t.BackgroundParameter2 = QtGui.QColor(0,0,64);
|
||||
t.BackgroundParameter3 = 0
|
||||
t.VerticalAlign = 2
|
||||
t.Name="stretch x"
|
||||
self.r.set_theme(t)
|
||||
self.answer=self.r.render_screen(0)
|
||||
self.expected_answer= QtCore.QRect(0, 129, 559, 342)
|
||||
self.bmpname=whoami()
|
||||
|
||||
def test_bg_shrink_x(self):
|
||||
t=Theme()
|
||||
t.BackgroundType = 2
|
||||
t.BackgroundParameter1 = os.path.join('data_for_tests', "treesbig.jpg");
|
||||
t.BackgroundParameter2 = QtGui.QColor(0,0,64);
|
||||
t.BackgroundParameter3 = 0
|
||||
t.VerticalAlign = 2
|
||||
t.Name="shrink x"
|
||||
self.r.set_theme(t)
|
||||
self.expected_answer= QtCore.QRect(0, 129, 559, 342)
|
||||
self.answer=self.r.render_screen(0)
|
||||
self.bmpname=whoami()
|
||||
# }}}
|
||||
# {{{ Vertical alignment
|
||||
def test_theme_vertical_align_top(self):
|
||||
t=Theme()
|
||||
t.BackgroundType = 0
|
||||
t.BackgroundParameter1 = QtGui.QColor(0,0,64);
|
||||
t.VerticalAlign = 0
|
||||
t.Name="valign top"
|
||||
self.r.set_theme(t)
|
||||
self.answer=self.r.render_screen(0)
|
||||
self.bmpname=whoami()
|
||||
|
||||
def test_theme_vertical_align_bot(self):
|
||||
t=Theme()
|
||||
t.BackgroundType = 0
|
||||
t.BackgroundParameter1 = QtGui.QColor(0,0,64);
|
||||
t.VerticalAlign = 1
|
||||
t.Name="valign bot"
|
||||
self.r.set_theme(t)
|
||||
self.answer=self.r.render_screen(0)
|
||||
self.expected_answer= QtCore.QRect(0, 257, 559, 342)
|
||||
self.bmpname=whoami()
|
||||
|
||||
def test_theme_vertical_align_cen(self):
|
||||
t=Theme()
|
||||
t.BackgroundType = 0
|
||||
t.BackgroundParameter1 = QtGui.QColor(0,0,64);
|
||||
t.VerticalAlign = 2
|
||||
t.Name="valign cen"
|
||||
self.r.set_theme(t)
|
||||
self.answer=self.r.render_screen(0)
|
||||
self.expected_answer= QtCore.QRect(0, 129, 559, 342)
|
||||
self.bmpname=whoami()
|
||||
# }}}
|
||||
# {{{ Horzontal alignment
|
||||
def test_theme_horizontal_align_left(self):
|
||||
t=Theme()
|
||||
t.BackgroundType = 0
|
||||
t.BackgroundParameter1 = QtGui.QColor(0,0,64);
|
||||
t.VerticalAlign = 0
|
||||
t.HorizontalAlign = 0
|
||||
t.Name="halign left"
|
||||
self.r.set_theme(t)
|
||||
self.answer=self.r.render_screen(0)
|
||||
self.bmpname=whoami()
|
||||
|
||||
def test_theme_horizontal_align_right(self):
|
||||
t=Theme()
|
||||
t.BackgroundType = 0
|
||||
t.BackgroundParameter1 = QtGui.QColor(0,0,64);
|
||||
t.VerticalAlign = 0
|
||||
t.HorizontalAlign = 1
|
||||
t.Name="halign right"
|
||||
self.r.set_theme(t)
|
||||
self.expected_answer= QtCore.QRect(0, 0, 800, 342)
|
||||
self.answer=self.r.render_screen(0)
|
||||
self.bmpname=whoami()
|
||||
|
||||
def test_theme_horizontal_align_centre(self):
|
||||
t=Theme()
|
||||
t.BackgroundType = 0
|
||||
t.BackgroundParameter1 = QtGui.QColor(0,0,64);
|
||||
t.VerticalAlign = 0
|
||||
t.HorizontalAlign = 2
|
||||
t.Name="halign centre"
|
||||
self.r.set_theme(t)
|
||||
self.answer=self.r.render_screen(0)
|
||||
self.expected_answer= QtCore.QRect(0, 0, 679, 342)
|
||||
self.bmpname=whoami()
|
||||
|
||||
def test_theme_horizontal_align_left_lyric(self):
|
||||
t=Theme()
|
||||
t.BackgroundType = 0
|
||||
t.BackgroundParameter1 = QtGui.QColor(0,0,64);
|
||||
t.VerticalAlign = 0
|
||||
t.HorizontalAlign = 0
|
||||
t.WrapStyle=1
|
||||
t.Name="halign left lyric"
|
||||
self.r.set_theme(t)
|
||||
self.answer=self.r.render_screen(0)
|
||||
self.expected_answer=QtCore.QRect(0, 0, 778, 342)
|
||||
self.bmpname=whoami()
|
||||
|
||||
# }}}
|
||||
# {{{ Shadows and outlines
|
||||
def test_theme_shadow_outline(self):
|
||||
t=Theme()
|
||||
t.BackgroundType = 0
|
||||
t.BackgroundParameter1 = QtGui.QColor(0,0,0);
|
||||
t.Name="shadow/outline"
|
||||
t.Shadow=1
|
||||
t.Outline=1
|
||||
t.ShadowColor=QtGui.QColor(64,128,0)
|
||||
t.OutlineColor=QtGui.QColor(128,0,0)
|
||||
self.r.set_debug(1)
|
||||
self.r.set_theme(t)
|
||||
self.answer=self.r.render_screen(0)
|
||||
hoffset=self.r._shadow_offset+2*(self.r._outline_offset)
|
||||
voffset=hoffset * (len(self.r.words[0])+1)
|
||||
|
||||
self.expected_answer= QtCore.QRect(0, 0, 559+hoffset, 342+voffset)
|
||||
self.bmpname=whoami()
|
||||
# }}}
|
||||
def test_theme_font(self):
|
||||
t=Theme()
|
||||
t.BackgroundType = 0
|
||||
t.BackgroundParameter1 = QtGui.QColor(0,0,64);
|
||||
t.Name="font"
|
||||
t.FontName="Times New Roman"
|
||||
self.r.set_theme(t)
|
||||
self.answer=self.r.render_screen(0)
|
||||
self.expected_answer= QtCore.QRect(0, 0, 499, 336)
|
||||
self.bmpname=whoami()
|
||||
|
||||
|
||||
if __name__=="__main__":
|
||||
t=TestRenderTheme()
|
||||
t.setup_class()
|
||||
t.setup_method(None)
|
||||
t.test_theme_font()
|
||||
t.teardown_method(None)
|
1
openlp/theme/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from theme import Theme
|
@ -1,10 +1,17 @@
|
||||
from theme import Theme
|
||||
import os
|
||||
import sys
|
||||
mypath=os.path.split(os.path.abspath(__file__))[0]
|
||||
|
||||
sys.path.insert(0,(os.path.join(mypath, '..' ,'..', '..')))
|
||||
print sys.path
|
||||
|
||||
from openlp.theme import Theme
|
||||
import os.path
|
||||
from PyQt4 import QtGui
|
||||
def test_read_theme():
|
||||
dir=os.path.split(__file__)[0]
|
||||
# test we can read a theme
|
||||
t=Theme(os.path.join(dir, "testtheme.xml"))
|
||||
t=Theme(os.path.join(dir, "test_theme.xml"))
|
||||
print t
|
||||
assert(t.BackgroundParameter1 == "sunset1.jpg")
|
||||
assert(t.BackgroundParameter2 == None)
|
||||
@ -39,5 +46,8 @@ def test_read_theme():
|
||||
|
||||
print "Tests passed"
|
||||
|
||||
def test_theme():
|
||||
test_read_theme()
|
||||
|
||||
if __name__=="__main__":
|
||||
test_read_theme()
|
@ -75,7 +75,7 @@ class Theme:
|
||||
t=''.join(file.readlines()) # read the file and change list to a string
|
||||
self._set_from_XML(t)
|
||||
|
||||
def get_as_string(self):
|
||||
def _get_as_string(self):
|
||||
s=""
|
||||
keys=dir(self)
|
||||
keys.sort()
|