Bump version number in the source code
[accentuate.git] / accentuate
blob3c38b505c784370cc336c2d31f927036a2d142eb
1 #!/usr/bin/env python
2 # -*- coding: iso-8859-1 -*-
4 # ACCENTUATE
5 # Based on K******, a fancy presentation tool, by Martin Fiedler
6 # Copyright (C) 2005-2007 Martin J. Fiedler <martin.fiedler@gmx.net>
7 # portions Copyright (C) 2005 Rob Reid <rreid@drao.nrc.ca>
8 # portions Copyright (C) 2006 Ronan Le Hy <rlehy@free.fr>
9 # portions Copyright (C) 2007 Luke Campagnola <luke.campagnola@gmail.com>
11 # New Maintainer of the Accentuate fork.
13 # portions Copyright (C) 2008 Jorden Mauro <jrm8005@gmail.com>
15 # This program is free software; you can redistribute it and/or modify
16 # it under the terms of the GNU General Public License, version 2, as
17 # published by the Free Software Foundation.
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # GNU General Public License for more details.
24 # You should have received a copy of the GNU General Public License
25 # along with this program; if not, write to the Free Software
26 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
28 __title__ = "Accentuate"
29 __version__ = "1.1"
30 __author__ = "Jorden Mauro (based on code by Martin Fiedler"
31 __email__ = "jrm8005@gmail.com"
32 __website__ = "http://www.cs.rit.edu/~jrm8005"
33 import sys
35 def greet(): print >>sys.stderr, "Welcome to", __title__, "version", __version__
36 if __name__ == "__main__": greet()
39 TopLeft, BottomLeft, TopRight, BottomRight, TopCenter, BottomCenter = range(6)
40 NoCache, MemCache, FileCache, PersistentCache = range(4) # for CacheMode
41 Off, First, Last = range(3) # for AutoOverview
43 # You may change the following lines to modify the default settings
44 Fullscreen = True
45 Scaling = False
46 Supersample = None
47 BackgroundRendering = True
48 UseGhostScript = False
49 UseAutoScreenSize = True
50 ScreenWidth = 1024
51 ScreenHeight = 768
52 TransitionDuration = 1000
53 MouseHideDelay = 3000
54 BoxFadeDuration = 100
55 ZoomDuration = 250
56 BlankFadeDuration = 250
57 MeshResX = 48
58 MeshResY = 36
59 MarkColor = (1.0, 0.0, 0.0, 0.1)
60 BoxEdgeSize = 4
61 SpotRadius = 64
62 SpotDetail = 16
63 CacheMode = FileCache
64 OverviewBorder = 3
65 AutoOverview = Off
66 InitialPage = None
67 Wrap = False
68 FadeToBlackAtEnd = False
69 AutoAdvance = None
70 RenderToDirectory = None
71 Rotation = 0
72 AllowExtensions = True
73 DAR = None
74 PAR = 1.0
75 PollInterval = 0
76 PageRangeStart = 0
77 PageRangeEnd = 999999
78 FontSize = 14
79 FontTextureWidth = 512
80 FontTextureHeight = 256
81 Gamma = 1.0
82 BlackLevel = 0
83 GammaStep = 1.1
84 BlackLevelStep = 8
85 EstimatedDuration = None
86 ProgressBarSize = 16
87 ProgressBarAlpha = 128
88 CursorImage = None
89 CursorHotspot = (0, 0)
90 MinutesOnly = False
91 OSDMargin = 16
92 OSDAlpha = 1.0
93 OSDTimePos = TopRight
94 OSDTitlePos = BottomLeft
95 OSDPagePos = BottomRight
96 OSDStatusPos = TopLeft
97 UseLIRC = False
98 PowerdotFix = False
100 # Support for LIRC remotes
101 try:
102 import pylirc, time, select
103 from threading import Thread
104 UseLIRC = True
105 except:
106 print "LIRC support unavailable."
107 UseLIRC = False
109 if(UseLIRC is True):
110 class IRRec(Thread):
111 def __init__(self):
112 Thread.__init__(self)
114 def run(self):
115 lirchandle = pylirc.init("Accentuate", '/etc/lircrc', 0)
116 print "Succesfully opened lirc, handle: " + str(lirchandle)
117 print "LIRC event loop started..."
119 # Man this is nasty indentation, but I use 8 space tabs
120 # while apparently most Pythoners use 3 :(
121 while(1):
122 # Read next code
123 s = pylirc.nextcode()
125 if(s):
126 # Handle the event
127 for (code) in s:
129 if(code == "next"):
130 pygame.event.post(pygame.event.Event(KEYDOWN, {'key': 275, 'unicode': u'', 'mod': 0}))
131 pygame.event.post(pygame.event.Event(KEYUP, {'key': 275, 'mod': 0}))
132 elif(code == "prev"):
133 pygame.event.post(pygame.event.Event(KEYDOWN, {'key': 276, 'unicode': u'', 'mod': 0}))
134 pygame.event.post(pygame.event.Event(KEYUP, {'key': 276, 'mod': 0}))
135 elif(code == "menu"):
136 pygame.event.post(pygame.event.Event(KEYDOWN, {'key': 9, 'unicode': u'\t', 'mod': 0}))
137 pygame.event.post(pygame.event.Event(KEYUP, {'key': 9, 'mod': 0}))
138 elif(code == "play"):
139 pygame.event.post(pygame.event.Event(KEYDOWN, {'key': 13, 'unicode': u'\r', 'mod': 0}))
140 pygame.event.post(pygame.event.Event(KEYUP, {'key': 13, 'mod': 0}))
141 elif(code == "volume_down"):
142 pygame.event.post(pygame.event.Event(KEYDOWN, {'key': 274, 'unicode': u'', 'mod': 0}))
143 pygame.event.post(pygame.event.Event(KEYUP, {'key': 274, 'mod': 0}))
144 elif(code == "volume_up"):
145 pygame.event.post(pygame.event.Event(KEYDOWN, {'key': 273, 'unicode': u'\r', 'mod': 0}))
146 pygame.event.post(pygame.event.Event(KEYUP, {'key': 273, 'mod': 0}))
148 time.sleep(1);
150 # End LIRC support code
152 # import basic modules
153 import random, getopt, os, types, re, codecs, tempfile, glob, StringIO, md5, re
154 import traceback
155 from math import *
157 # initialize some platform-specific settings
158 if os.name == "nt":
159 root = os.path.split(sys.argv[0])[0] or "."
160 pdftoppmPath = os.path.join(root, "pdftoppm.exe")
161 GhostScriptPath = os.path.join(root, "gs\\gswin32c.exe")
162 GhostScriptPlatformOptions = ["-I" + os.path.join(root, "gs")]
163 try:
164 import win32api
165 MPlayerPath = os.path.join(root, "mplayer.exe")
166 def GetScreenSize():
167 dm = win32api.EnumDisplaySettings(None, -1) #ENUM_CURRENT_SETTINGS
168 return (int(dm.PelsWidth), int(dm.PelsHeight))
169 def RunURL(url):
170 win32api.ShellExecute(0, "open", url, "", "", 0)
171 except ImportError:
172 MPlayerPath = ""
173 def GetScreenSize(): return pygame.display.list_modes()[0]
174 def RunURL(url): print "Error: cannot run URL `%s'" % url
175 MPlayerPlatformOptions = [ "-colorkey", "0x000000" ]
176 MPlayerColorKey = True
177 pdftkPath = os.path.join(root, "pdftk.exe")
178 FileNameEscape = '"'
179 spawn = os.spawnv
180 if getattr(sys, "frozen", None):
181 sys.path.append(root)
182 FontPath = []
183 FontList = ["Verdana.ttf", "Arial.ttf"]
184 else:
185 pdftoppmPath = "pdftoppm"
186 GhostScriptPath = "gs"
187 GhostScriptPlatformOptions = []
188 MPlayerPath = "mplayer"
189 MPlayerPlatformOptions = [ "-vo", "gl" ]
190 MPlayerColorKey = False
191 pdftkPath = "pdftk"
192 spawn = os.spawnvp
193 FileNameEscape = ""
194 FontPath = ["/usr/share/fonts", "/usr/local/share/fonts", "/usr/X11R6/lib/X11/fonts/TTF"]
195 FontList = ["DejaVuSans.ttf", "Vera.ttf", "Verdana.ttf"]
196 def RunURL(url):
197 try:
198 spawn(os.P_NOWAIT, "xdg-open", ["xdg-open", url])
199 except OSError:
200 print >>sys.stderr, "Error: cannot open URL `%s'" % url
201 def GetScreenSize():
202 res_re = re.compile(r'\s*(\d+)x(\d+)\s+\d+\.\d+\*')
203 for path in os.getenv("PATH").split(':'):
204 fullpath = os.path.join(path, "xrandr")
205 if os.path.exists(fullpath):
206 res = None
207 try:
208 for line in os.popen(fullpath, "r"):
209 m = res_re.match(line)
210 if m:
211 res = tuple(map(int, m.groups()))
212 except OSError:
213 pass
214 if res:
215 return res
216 return pygame.display.list_modes()[0]
218 # import special modules
219 try:
220 from OpenGL.GL import *
221 import pygame
222 from pygame.locals import *
223 import Image, ImageDraw, ImageFont, ImageFilter
224 import TiffImagePlugin, BmpImagePlugin, JpegImagePlugin, PngImagePlugin, PpmImagePlugin
225 except (ValueError, ImportError), err:
226 print >>sys.stderr, "Oops! Cannot load necessary modules:", err
227 print >>sys.stderr, """To use Accentuate, you need to install the following Python modules:
228 - PyOpenGL [python-opengl] http://pyopengl.sourceforge.net/
229 - PyGame [python-pygame] http://www.pygame.org/
230 - PIL [python-imaging] http://www.pythonware.com/products/pil/
231 - PyWin32 (OPTIONAL, Win32) http://starship.python.net/crew/mhammond/win32/
232 Additionally, please be sure to have pdftoppm or GhostScript installed if you
233 intend to use PDF input."""
234 sys.exit(1)
236 try:
237 import thread
238 EnableBackgroundRendering = True
239 def create_lock(): return thread.allocate_lock()
240 except ImportError:
241 EnableBackgroundRendering = False
242 class pseudolock:
243 def __init__(self): self.state = False
244 def acquire(self, dummy=0): self.state = True
245 def release(self): self.state = False
246 def locked(self): return self.state
247 def create_lock(): return pseudolock()
250 ##### TOOL CODE ################################################################
252 # initialize private variables
253 FileName = ""
254 FileList = []
255 InfoScriptPath = None
256 Marking = False
257 Tracing = False
258 Panning = False
259 FileProps = {}
260 PageProps = {}
261 PageCache = {}
262 CacheFile = None
263 CacheFileName = None
264 CacheFilePos = 0
265 CacheMagic = ""
266 MPlayerPID = 0
267 VideoPlaying = False
268 MouseDownX = 0
269 MouseDownY = 0
270 MarkUL = (0, 0)
271 MarkLR = (0, 0)
272 ZoomX0 = 0.0
273 ZoomY0 = 0.0
274 ZoomArea = 1.0
275 ZoomMode = False
276 IsZoomed = False
277 ZoomWarningIssued = False
278 TransitionRunning = False
279 CurrentCaption = 0
280 OverviewNeedUpdate = False
281 FileStats = None
282 OSDFont = None
283 CurrentOSDCaption = ""
284 CurrentOSDPage = ""
285 CurrentOSDStatus = ""
286 CurrentOSDComment = ""
287 Lrender = create_lock()
288 Lcache = create_lock()
289 Loverview = create_lock()
290 RTrunning = False
291 RTrestart = False
292 StartTime = 0
293 CurrentTime = 0
294 PageEnterTime = 0
295 TimeDisplay = False
296 TimeTracking = False
297 FirstPage = True
298 ProgressBarPos = 0
299 CursorVisible = True
300 OverviewMode = False
301 LastPage = 0
302 WantStatus = False
304 # tool constants (used in info scripts)
305 FirstTimeOnly = 2
307 # event constants
308 USEREVENT_HIDE_MOUSE = USEREVENT
309 USEREVENT_PAGE_TIMEOUT = USEREVENT + 1
310 USEREVENT_POLL_FILE = USEREVENT + 2
311 USEREVENT_TIMER_UPDATE = USEREVENT + 3
314 # read and write the PageProps and FileProps meta-dictionaries
315 def GetProp(prop_dict, key, prop, default=None):
316 if not key in prop_dict: return default
317 if type(prop) == types.StringType:
318 return prop_dict[key].get(prop, default)
319 for subprop in prop:
320 try:
321 return prop_dict[key][subprop]
322 except KeyError:
323 pass
324 return default
325 def SetProp(prop_dict, key, prop, value):
326 if not key in prop_dict:
327 prop_dict[key] = {prop: value}
328 else:
329 prop_dict[key][prop] = value
331 def GetPageProp(page, prop, default=None):
332 global PageProps
333 return GetProp(PageProps, page, prop, default)
334 def SetPageProp(page, prop, value):
335 global PageProps
336 SetProp(PageProps, page, prop, value)
337 def GetTristatePageProp(page, prop, default=0):
338 res = GetPageProp(page, prop, default)
339 if res != FirstTimeOnly: return res
340 return (GetPageProp(page, '_shown', 0) == 1)
342 def GetFileProp(page, prop, default=None):
343 global FileProps
344 return GetProp(FileProps, page, prop, default)
345 def SetFileProp(page, prop, value):
346 global FileProps
347 SetProp(FileProps, page, prop, value)
349 # the Accentuate logo (256x64 pixels grayscale PNG)
350 LOGO = '\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x01\x00\x00\x00\x00@\x08\x00\x00\x00\x00\xd06\xf6b\x00'+ \
351 '\x00\x00\x01sRGB\x00\xae\xce\x1c\xe9\x00\x00\x00\tpHYs\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a'+ \
352 '\x9c\x18\x00\x00\x00\x07tIME\x07\xd8\t\x01\x10\x06\x0bZ\xbb\xec\x88\x00\x00\x00\x19tEXtComment'+ \
353 '\x00Created with GIMPW\x81\x0e\x17\x00\x00\x05TIDATx\xda\xedXkL\\E\x18=\xfb\xa0\xbc\x16\x02\x94W'+ \
354 '\xdd"4\x88\x8d\x85\xb6P\x8b\xa9\xa6\xb5)-\x11\xaa\xa8\xa4\xc4Tc\xd26bc\xd5h\xc4\x1a\x8d\xa6\x06\xac5FM'+ \
355 '\x94\xd8\x88\xb1\xc1\xbe\xb4\xbe\xfda1\xd1\xa8\xd8\x07M\x9a`\x81D\xc4\x18\x0cH\x1f\xb6\xa6\xa5\xd8'+ \
356 '\x07\xb0@w\xaf?\xbeYv\xe6\xee\x9d\xb9+\x90\xf4\xc7\xce\xf9\xc3|\xe7\x9b\xfb\xdds\xcf^\xbe\x99\xb9\x80'+ \
357 '\x86\x86\x86\x86\x86\x86\x86\x86\x86\x86\x86\x86\x86\x86\x86\x86F\xf4b\xb9\x11\xc4\xf2\xe8yh\'7\xde49'+ \
358 '\xdax]\xb40\xf7\xaf[\xa5\xc4+\x93o\xc0\xe5\x84\xa81\x80{\x03j<\x93\xc3\xa4\x9ahl\x01?\x1b!\xb4F\xe1'+ \
359 '\xbf\xc0\xbc\x00g@ /Z\x0cp\x87\x1a\x9f\x03\x000\x1a\x0f\x00\x8e\x8d\xf5\xa6\x89q\x95\xe5\xc5\xb9i'+ \
360 '\xb3|\x83\xa7\xba\xdb[\xfb\xec\xf9\x8c\xeaU\x0b\xbd\x9e\xc0`\xef\xb1/:\xadn\xec}\xac\xf2\xe6\x84\xa1'+ \
361 '\x9eov]\x9e\xfas\xban[\xb3t~\xb6\xc7\x18\xb9\xd0\xd7\xf9C\xab\xdfv\xbeR\x93\xa3\x9f\\\xdbB\x7f\xfa'+ \
362 '\x1cB6\xa1\xfe"\xf7~\x18\xb6|F\x93\x8f\xfb\x7fZ\x18\xfe\xd3l\x1de\xc3s+\x85\x8cX\xcbTV\x0c\x97'+ \
363 '\xbc\'\xdc\xfb\xe4&(*)41\x94Q\xa2\x17=4X\xc5\'\x17\xfdiYR\xca\xaf\xfcG\xa0}\x1bL\x8f\xe0\xdc\x1bJ'+ \
364 '\x8e\xdc:E\x03\xc2\xae\xd8\xe7T\x19 \xd5\xc4\xb0\x9f\xf8z\xbc\xc8\xaaq\xb9\xd2K\xd6\x9e\xca\xf82'+ \
365 '\x9fY\xc3\x06Q\xf4N>\xd7\xe5\x98!\x03\x8cm\n\x03\xe4\x9a\xd8\xc27L\xecM\xc8\xa3f8\x9c\x14z\x9f\xff'+ \
366 '\xb6\xf6T\xc6{\x07\xc34\xf8\x8ad\xa2\r\xc3\xa8\x98)\x03F\xb3\xa4\x95\x14\x9a\x08\xb5D\x1e\x07\xd0F'+ \
367 '\xc3G&s\xcd\xec\x8a\x0b\xdb\x8a\x93]\xde\xfb?\x1a3\xd4\xfc\x01\xf6n7\xdc\x12\x9b\xbc\xb6\x83\x82'+ \
368 '\x9fL\xa2\xf7\x14\xc7\xe5\xbe\xc3\xc6\xef\xdb\x18\x10\x90\x19\xd0\xbd\xe3\xee\xfcDg\xacwu\xd3\x04e'+ \
369 '\xea\xa4\x95\x14\x9a\x08\xc7\x88{\x12\x00k\x83m\xc1\xd4\\V\xbd#\x9b\x119_*\xf9<?\x19\xbc\x0c\x00'+ \
370 '\x10\xdfN\xb3\x8a\x05\x03\x1ax\x07OH\x17/"\xaeY\x1b\xf0\xdd\xed\xdc.\x9e2\xdf\xca*\xa94\x01\x00\n'+ \
371 '\x88\x99\xc8\x000{\x9c\x82\x02\x96\xab\xa3\xf0\xdf\xb9\xa6\xae!\xe3\x9f#\xfe\r\x16\x96S\xf8*\xaf'+ \
372 '\xac\xc7\x05\x00XA\xd1\xa0\x8d\x01\x13\xf6\xcb{\x1ce\xfae\x13U\x9a\x00\x00\xaf\xf1\x0e\x1e\xa4`\x07'+ \
373 '\xcb\xb1\xf0u\xf3Me|\x0b\xf1%\xc1#\x06\x85\x87xeu\x94\x9a#\xfe\xc22\x03\xc6e\x06d?\xba\xfb\xf8\x99a?'+ \
374 '\xb7\xa2\xc8*\xa94\x01\x80\xf341\x0f\x02\x00\xd6Sp\x8a\x9d\x13\xfa(\xbc\xc3\xfc\xa02\xbe\xdf\xb2\xd3'+ \
375 '\x9d\xe1\x95-\x15t\x186\x06\xf8\xac\r\xf0~|\xcd|\x93\x80\xac\x92J\x13\x00T\x08\x87\xc0\x04v,\xbc\x8b'+ \
376 '\x92#\x14\xa5\x9a\x1f\xd4\x867\xf7\\^Y\n\xdb\x86N\xc7\x80\xe2\xf3\x16w\x91URi\x02\x80O\x89\xd8\xcb\xc2}'+ \
377 '\x14~B\x11;#\xb8\xcd\x0fj\xc3[jcc\x17\xdb}N\xc3\x80\xc4\x01\xe3\x7f\x18\xa0\xd2\x04 \xd5g\x99\x1eM\x99'+ \
378 '\xc17\xc0@\xa4\x0b\xbb\xb4\tz\xf8\t\xcf\xd0\xd8\xdfX\x9a\x14A\xa5\x11\xb5\x01\x8f[\xa7\x8d-\xd3\xe9\x01'+ \
379 '\x19\x11\x1c\xd3"3\xc0`\xcdh>?\xe1\xb0\xd0N\x93m*\xc959U_\xc0\x88\xff\x8d\x82*sV\xc6\xff\xce\xbe0\xce'+ \
380 '\xc4\xb1\x96\xfe\xb0\x96!X\xcd\xb6q\x1f\xb2\x86`SH\xad\xa9\xd0\x90a\x01\x00<K\xe3\xa1\x1bL\x97\xc9\xf8'+ \
381 '\xe7-\xbe\xa9\x14\x1c\x8c\xe0\r\xf0\x0b\xbf7\x00\xb0n\\JQ\x1b?\x9dMN\x146\x9f\xd2J*M\xc0[R\x03\xde\xe4w|'+ \
382 '\xed\x99l~\xd6\xe7P\xf1\xf3\xd8\xed_\t\xfdZ\xcd\x13F\x04\x06\xb0\xa3U~H#;\x99\xbe\r\x00xB\xf8\xcf\xbdJ'+ \
383 '\xe3\x87\x00\x00\x0f\x1b6\x95T\x9a\xe0>G\xc9Z\xfe\xe5\'\xea\xac\x0b\x00v\xb3\xea\xe7_Z\xecqeW|0\xc2'+ \
384 '\xae\x94\xf1\x9f1\xfe\xc8\xfa\x9cY1\xde\xd5/\xb7\x87\xa4)\r\xf8\x83\xa2\x96\xa2X\x88\xcbQ`\xbb7\xb6\xf0]'+ \
385 '\xbf\xf0\x94\'h|isz\xcc\x82F\xbfaWI\xa1\tU\xac\x9ffr\x06\xa4\xb3=\xc6=\x00\x90y\xd6\xba{\xca\xf8\x9c\x8b'+ \
386 '\xd2\x86\xab4\xe0@\xd8\x055\xf2\xde\xdd\xa0j\xeb\xe1\x95\x14\x9a\xf0\xb5\xe9\xf0\x03\x008B\xe4W\x00\x80eW'+ \
387 '\xac\xaf\x94\xf1\xe5\xbe)\x19\xf0@\xd8\x05n\xe1\x83\xcbN~z\x96\xf0-b\x8f]%\x85\xa6tv\xf6\xd9ju\xd2\x19\x9b'+ \
388 '\r\x00(\xe9\xb3^?e\xfc\x9d\xe6w\xa3\xbb2\x02\x03b:\xc3j\xad\x18\x0f\x11\xfb]\xc2\xf4u\xdc\x19\xe0P\x9cm%'+ \
389 '\xb9\xa6\xa7\x8d\xe0\xa7\x10\x1e\xf9\x8c}\x8a\xed\xda\xb7\x0fY~\xfb\x93\xf1i\x8d\xc3\xdc\x0e\xfd\xf0:g$'+ \
390 '\xfb\x00\xe4v\x84\xd5\xaa\x08nx}/8L\xd3\xef\x9b\xfc\xc6\xd1\x1c\x17A%\x89&\x07\xba\x16\xd3\xaa^$.f\xdd'+ \
391 '\x85\x00\x80\xae\xe0\x01*~\xed\x9a\x92\xdc4\xf7\xd8\xe0\xc9\x9e\xf6\x1f\xb9\xaf\xbf2>\xf9\xde\xb2E7'+ \
392 '\xa6\xb8\xae\x9e\xfe\xf5h\xcb\x80yawX\x87pWW/\x99\xe3q\xf2djmUa\xcah\xef\xf7M\x03a\xd3S7W\x15&\x8f'+ \
393 '\xfeu\xb4\xf9\x97\x88*\xc94ihhhhhhhhhhhhhhhD\x19\xfe\x03\xec\xf7\xad7pH,\x9f\x00\x00\x00\x00IEND\xaeB`\x82'
395 # determine the next power of two
396 def npot(x):
397 res = 1
398 while res < x: res <<= 1
399 return res
401 # convert boolean value to string
402 def b2s(b):
403 if b: return "Y"
404 return "N"
406 # extract a number at the beginning of a string
407 def num(s):
408 s = s.strip()
409 r = ""
410 while s[0] in "0123456789":
411 r += s[0]
412 s = s[1:]
413 try:
414 return int(r)
415 except ValueError:
416 return -1
418 # get a representative subset of file statistics
419 def my_stat(filename):
420 try:
421 s = os.stat(filename)
422 except OSError:
423 return None
424 return (s.st_size, s.st_mtime, s.st_ctime, s.st_mode)
426 # determine (pagecount,width,height) of a PDF file
427 def analyze_pdf(filename):
428 f = file(filename,"rb")
429 pdf = f.read()
430 f.close()
431 box = map(float, pdf.split("/MediaBox",1)[1].split("]",1)[0].split("[",1)[1].strip().split())
432 return (max(map(num, pdf.split("/Count")[1:])), box[2]-box[0], box[3]-box[1])
434 # unescape &#123; literals in PDF files
435 re_unescape = re.compile(r'&#[0-9]+;')
436 def decode_literal(m):
437 try:
438 return chr(int(m.group(0)[2:-1]))
439 except ValueError:
440 return '?'
441 def unescape_pdf(s):
442 return re_unescape.sub(decode_literal, s)
444 # parse pdftk output
445 def pdftkParse(filename, page_offset=0):
446 f = file(filename, "r")
447 InfoKey = None
448 BookmarkTitle = None
449 Title = None
450 Pages = 0
451 for line in f.xreadlines():
452 try:
453 key, value = [item.strip() for item in line.split(':', 1)]
454 except IndexError:
455 continue
456 key = key.lower()
457 if key == "numberofpages":
458 Pages = int(value)
459 elif key == "infokey":
460 InfoKey = value.lower()
461 elif (key == "infovalue") and (InfoKey == "title"):
462 Title = unescape_pdf(value)
463 InfoKey = None
464 elif key == "bookmarktitle":
465 BookmarkTitle = unescape_pdf(value)
466 elif key == "bookmarkpagenumber" and BookmarkTitle:
467 try:
468 page = int(value)
469 if not GetPageProp(page + page_offset, '_title'):
470 SetPageProp(page + page_offset, '_title', BookmarkTitle)
471 except ValueError:
472 pass
473 BookmarkTitle = None
474 f.close()
475 if AutoOverview:
476 SetPageProp(page_offset + 1, '_overview', True)
477 for page in xrange(page_offset + 2, page_offset + Pages):
478 SetPageProp(page, '_overview', \
479 not(not(GetPageProp(page + AutoOverview - 1, '_title'))))
480 SetPageProp(page_offset + Pages, '_overview', True)
481 return (Title, Pages)
483 # translate pixel coordinates to normalized screen coordinates
484 def MouseToScreen(mousepos):
485 return (ZoomX0 + mousepos[0] * ZoomArea / ScreenWidth,
486 ZoomY0 + mousepos[1] * ZoomArea / ScreenHeight)
488 # normalize rectangle coordinates so that the upper-left point comes first
489 def NormalizeRect(X0, Y0, X1, Y1):
490 return (min(X0, X1), min(Y0, Y1), max(X0, X1), max(Y0, Y1))
492 # check if a point is inside a box (or a list of boxes)
493 def InsideBox(x, y, box):
494 return (x >= box[0]) and (y >= box[1]) and (x < box[2]) and (y < box[3])
495 def FindBox(x, y, boxes):
496 for i in xrange(len(boxes)):
497 if InsideBox(x, y, boxes[i]):
498 return i
499 raise ValueError
501 # zoom an image size to a destination size, preserving the aspect ratio
502 def ZoomToFit(size, dest=None):
503 if not dest:
504 dest = (ScreenWidth, ScreenHeight)
505 newx = dest[0]
506 newy = size[1] * newx / size[0]
507 if newy > dest[1]:
508 newy = dest[1]
509 newx = size[0] * newy / size[1]
510 return (newx, newy)
512 # get the overlay grid screen coordinates for a specific page
513 def OverviewPos(page):
514 return ( \
515 int(page % OverviewGridSize) * OverviewCellX + OverviewOfsX, \
516 int(page / OverviewGridSize) * OverviewCellY + OverviewOfsY \
519 def StopMPlayer():
520 global MPlayerPID, VideoPlaying
521 if not MPlayerPID: return
522 try:
523 if os.name == 'nt':
524 win32api.TerminateProcess(MPlayerPID, 0)
525 else:
526 os.kill(MPlayerPID, 2)
527 MPlayerPID = 0
528 except:
529 pass
530 VideoPlaying = False
532 def FormatTime(t, minutes=False):
533 if minutes and (t < 3600):
534 return "%d min" % (t / 60)
535 elif minutes:
536 return "%d:%02d" % (t / 3600, (t / 60) % 60)
537 elif t < 3600:
538 return "%d:%02d" % (t / 60, t % 60)
539 else:
540 ms = t % 3600
541 return "%d:%02d:%02d" % (t / 3600, ms / 60, ms % 60)
543 def SafeCall(func, args=[], kwargs={}):
544 if not func: return None
545 try:
546 return func(*args, **kwargs)
547 except:
548 print >>sys.stderr, "----- Exception in user function ----"
549 traceback.print_exc(file=sys.stderr)
550 print >>sys.stderr, "----- End of traceback -----"
552 def Quit(code=0):
553 print >>sys.stderr, "Total presentation time: %s." % \
554 FormatTime((pygame.time.get_ticks() - StartTime) / 1000)
555 sys.exit(code)
558 ##### RENDERING TOOL CODE ######################################################
560 # draw a fullscreen quad
561 def DrawFullQuad():
562 glBegin(GL_QUADS)
563 glTexCoord2d( 0.0, 0.0); glVertex2i(0, 0)
564 glTexCoord2d(TexMaxS, 0.0); glVertex2i(1, 0)
565 glTexCoord2d(TexMaxS, TexMaxT); glVertex2i(1, 1)
566 glTexCoord2d( 0.0, TexMaxT); glVertex2i(0, 1)
567 glEnd()
569 # draw a generic 2D quad
570 def DrawQuad(x0=0.0, y0=0.0, x1=1.0, y1=1.0):
571 glBegin(GL_QUADS)
572 glTexCoord2d( 0.0, 0.0); glVertex2d(x0, y0)
573 glTexCoord2d(TexMaxS, 0.0); glVertex2d(x1, y0)
574 glTexCoord2d(TexMaxS, TexMaxT); glVertex2d(x1, y1)
575 glTexCoord2d( 0.0, TexMaxT); glVertex2d(x0, y1)
576 glEnd()
578 # helper function: draw a translated fullscreen quad
579 def DrawTranslatedFullQuad(dx, dy, i, a):
580 glColor4d(i, i, i, a)
581 glPushMatrix()
582 glTranslated(dx, dy, 0.0)
583 DrawFullQuad()
584 glPopMatrix()
586 # draw a vertex in normalized screen coordinates,
587 # setting texture coordinates appropriately
588 def DrawPoint(x, y):
589 glTexCoord2d(x *TexMaxS, y * TexMaxT)
590 glVertex2d(x, y)
591 def DrawPointEx(x, y, a):
592 glColor4d(1.0, 1.0, 1.0, a)
593 glTexCoord2d(x * TexMaxS, y * TexMaxT)
594 glVertex2d(x, y)
596 # a mesh transformation function: it gets the relative transition time (in the
597 # [0.0,0.1) interval) and the normalized 2D screen coordinates, and returns a
598 # 7-tuple containing the desired 3D screen coordinates, 2D texture coordinates,
599 # and intensity/alpha color values.
600 def meshtrans_null(t, u, v):
601 return (u, v, 0.0, u, v, 1.0, t)
602 # (x, y, z, s, t, i, a)
604 # draw a quad, applying a mesh transformation function
605 def DrawMeshQuad(time=0.0, f=meshtrans_null):
606 line0 = [f(time, u * MeshStepX, 0.0) for u in xrange(MeshResX + 1)]
607 for v in xrange(1, MeshResY + 1):
608 line1 = [f(time, u * MeshStepX, v * MeshStepY) for u in xrange(MeshResX + 1)]
609 glBegin(GL_QUAD_STRIP)
610 for col in zip(line0, line1):
611 for x, y, z, s, t, i, a in col:
612 glColor4d(i, i, i, a)
613 glTexCoord2d(s * TexMaxS, t * TexMaxT)
614 glVertex3d(x, y, z)
615 glEnd()
616 line0 = line1
618 def GenerateSpotMesh():
619 global SpotMesh
620 rx0 = SpotRadius * PixelX
621 ry0 = SpotRadius * PixelY
622 rx1 = (SpotRadius + BoxEdgeSize) * PixelX
623 ry1 = (SpotRadius + BoxEdgeSize) * PixelY
624 steps = max(6, int(2.0 * pi * SpotRadius / SpotDetail / ZoomArea))
625 SpotMesh=[(rx0 * sin(a), ry0 * cos(a), rx1 * sin(a), ry1 * cos(a)) for a in \
626 [i * 2.0 * pi / steps for i in range(steps + 1)]]
629 ##### TRANSITIONS ##############################################################
631 # Each transition is represented by a class derived from Accentuate.Transition
632 # The interface consists of only two methods: the __init__ method may perform
633 # some transition-specific initialization, and render() finally renders a frame
634 # of the transition, using the global texture identifierst Tcurrent and Tnext.
636 # Transition itself is an abstract class
637 class AbstractError(StandardError):
638 pass
639 class Transition:
640 def __init__(self):
641 pass
642 def render(self, t):
643 raise AbstractError
645 # an array containing all possible transition classes
646 AllTransitions=[]
648 # a helper function doing the common task of directly blitting a background page
649 def DrawPageDirect(tex):
650 glDisable(GL_BLEND)
651 glBindTexture(TextureTarget, tex)
652 glColor3d(1, 1, 1)
653 DrawFullQuad()
655 # a helper function that enables alpha blending
656 def EnableAlphaBlend():
657 glEnable(GL_BLEND)
658 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
661 # Crossfade: one of the simplest transition you can think of :)
662 class Crossfade(Transition):
663 """simple crossfade"""
664 def render(self,t):
665 DrawPageDirect(Tcurrent)
666 EnableAlphaBlend()
667 glBindTexture(TextureTarget, Tnext)
668 glColor4d(1, 1, 1, t)
669 DrawFullQuad()
670 AllTransitions.append(Crossfade)
673 # Slide: a class of transitions that simply slide the new page in from one side
674 # after an idea from Joachim B Haga
675 class Slide(Transition):
676 def origin(self, t):
677 raise AbstractError
678 def render(self, t):
679 cx, cy, nx, ny = self.origin(t)
680 glBindTexture(TextureTarget, Tcurrent)
681 DrawQuad(cx, cy, cx+1.0, cy+1.0)
682 glBindTexture(TextureTarget, Tnext)
683 DrawQuad(nx, ny, nx+1.0, ny+1.0)
685 class SlideLeft(Slide):
686 """Slide to the left"""
687 def origin(self, t): return (-t, 0.0, 1.0-t, 0.0)
688 class SlideRight(Slide):
689 """Slide to the right"""
690 def origin(self, t): return (t, 0.0, t-1.0, 0.0)
691 class SlideUp(Slide):
692 """Slide upwards"""
693 def origin(self, t): return (0.0, -t, 0.0, 1.0-t)
694 class SlideDown(Slide):
695 """Slide downwards"""
696 def origin(self, t): return (0.0, t, 0.0, t-1.0)
697 AllTransitions.extend([SlideLeft, SlideRight, SlideUp, SlideDown])
700 # Squeeze: a class of transitions that squeeze the new page in from one size
701 class Squeeze(Transition):
702 def params(self, t):
703 raise AbstractError
704 def inv(self): return 0
705 def render(self, t):
706 cx1, cy1, nx0, ny0 = self.params(t)
707 if self.inv():
708 t1, t2 = (Tnext, Tcurrent)
709 else:
710 t1, t2 = (Tcurrent, Tnext)
711 glBindTexture(TextureTarget, t1)
712 DrawQuad(0.0, 0.0, cx1, cy1)
713 glBindTexture(TextureTarget, t2)
714 DrawQuad(nx0, ny0, 1.0, 1.0)
715 class SqueezeHorizontal(Squeeze):
716 def split(self, t): raise AbstractError
717 def params(self, t):
718 t = self.split(t)
719 return (t, 1.0, t, 0.0)
720 class SqueezeVertical(Squeeze):
721 def split(self, t): raise AbstractError
722 def params(self, t):
723 t = self.split(t)
724 return (1.0, t, 0.0, t)
726 class SqueezeLeft(SqueezeHorizontal):
727 """Squeeze to the left"""
728 def split(self, t): return 1.0 - t
729 class SqueezeRight(SqueezeHorizontal):
730 """Squeeze to the right"""
731 def split(self, t): return t
732 def inv(self): return 1
733 class SqueezeUp(SqueezeVertical):
734 """Squeeze upwards"""
735 def split(self, t): return 1.0 - t
736 class SqueezeDown(SqueezeVertical):
737 """Squeeze downwards"""
738 def split(self, t): return t
739 def inv(self): return 1
740 AllTransitions.extend([SqueezeLeft, SqueezeRight, SqueezeUp, SqueezeDown])
743 # Wipe: a class of transitions that softly "wipe" the new image over the old
744 # one along a path specified by a gradient function that maps normalized screen
745 # coordinates to a number in the range [0.0,1.0]
746 WipeWidth = 0.25
747 class Wipe(Transition):
748 def grad(self, u, v):
749 raise AbstractError
750 def afunc(self, g):
751 pos = (g - self.Wipe_start) / WipeWidth
752 return max(min(pos, 1.0), 0.0)
753 def render(self, t):
754 DrawPageDirect(Tnext)
755 EnableAlphaBlend()
756 glBindTexture(TextureTarget, Tcurrent)
757 self.Wipe_start = t * (1.0 + WipeWidth) - WipeWidth
758 DrawMeshQuad(t, lambda t, u, v: \
759 (u, v, 0.0, u,v, 1.0, self.afunc(self.grad(u, v))))
761 class WipeDown(Wipe):
762 """wipe downwards"""
763 def grad(self, u, v): return v
764 class WipeUp(Wipe):
765 """wipe upwards"""
766 def grad(self, u, v): return 1.0 - v
767 class WipeRight(Wipe):
768 """wipe from left to right"""
769 def grad(self, u, v): return u
770 class WipeLeft(Wipe):
771 """wipe from right to left"""
772 def grad(self, u, v): return 1.0 - u
773 class WipeDownRight(Wipe):
774 """wipe from the upper-left to the lower-right corner"""
775 def grad(self, u, v): return 0.5 * (u + v)
776 class WipeUpLeft(Wipe):
777 """wipe from the lower-right to the upper-left corner"""
778 def grad(self, u, v): return 1.0 - 0.5 * (u + v)
779 class WipeCenterOut(Wipe):
780 """wipe from the center outwards"""
781 def grad(self, u, v):
782 u -= 0.5
783 v -= 0.5
784 return sqrt(u * u * 1.777 + v * v) / 0.833
785 class WipeCenterIn(Wipe):
786 """wipe from the edges inwards"""
787 def grad(self, u, v):
788 u -= 0.5
789 v -= 0.5
790 return 1.0 - sqrt(u * u * 1.777 + v * v) / 0.833
791 AllTransitions.extend([WipeDown, WipeUp, WipeRight, WipeLeft, \
792 WipeDownRight, WipeUpLeft, WipeCenterOut, WipeCenterIn])
794 class WipeBlobs(Wipe):
795 """wipe using nice \"blob\"-like patterns"""
796 def __init__(self):
797 self.uscale = (5.0 + random.random() * 15.0) * 1.333
798 self.vscale = 5.0 + random.random() * 15.0
799 self.uofs = random.random() * 6.2
800 self.vofs = random.random() * 6.2
801 def grad(self,u,v):
802 return 0.5 + 0.25 * (cos(self.uofs + u * self.uscale) \
803 + cos(self.vofs + v * self.vscale))
804 AllTransitions.append(WipeBlobs)
806 class PagePeel(Transition):
807 """an unrealistic, but nice page peel effect"""
808 def render(self,t):
809 glDisable(GL_BLEND)
810 glBindTexture(TextureTarget, Tnext)
811 DrawMeshQuad(t, lambda t, u, v: \
812 (u, v, 0.0, u, v, 1.0 - 0.5 * (1.0 - u) * (1.0 - t), 1.0))
813 EnableAlphaBlend()
814 glBindTexture(TextureTarget, Tcurrent)
815 DrawMeshQuad(t, lambda t, u, v: \
816 (u * (1.0 - t), 0.5 + (v - 0.5) * (1.0 + u * t) * (1.0 + u * t), 0.0,
817 u, v, 1.0 - u * t * t, 1.0))
818 AllTransitions.append(PagePeel)
820 ### additional transition by Ronan Le Hy <rlehy@free.fr> ###
822 class PageTurn(Transition):
823 """another page peel effect, slower but more realistic than PagePeel"""
824 alpha = 2.
825 alpha_square = alpha * alpha
826 sqrt_two = sqrt(2.)
827 inv_sqrt_two = 1. / sqrt(2.)
828 def warp(self, t, u, v):
829 # distance from the 2d origin to the folding line
830 dpt = PageTurn.sqrt_two * (1.0 - t)
831 # distance from the 2d origin to the projection of (u,v) on the folding line
832 d = PageTurn.inv_sqrt_two * (u + v)
833 dmdpt = d - dpt
834 # the smaller rho is, the closer to asymptotes are the x(u) and y(v) curves
835 # ie, smaller rho => neater fold
836 rho = 0.001
837 common_sq = sqrt(4. - 8 * t - 4.*(u+v) + 4.*t*(t + v + u) + (u+v)*(u+v) + 4 * rho) / 2.
838 x = 1. - t + 0.5 * (u - v) - common_sq
839 y = 1. - t + 0.5 * (v - u) - common_sq
840 z = - 0.5 * (PageTurn.alpha * dmdpt + sqrt(PageTurn.alpha_square * dmdpt*dmdpt + 4))
841 if dmdpt < 0:
842 # part of the sheet still flat on the screen: lit and opaque
843 i = 1.0
844 alpha = 1.0
845 else:
846 # part of the sheet in the air, after the fold: shadowed and transparent
847 # z goes from -0.8 to -2 approximately
848 i = -0.5 * z
849 alpha = 0.5 * z + 1.5
850 # the corner of the page that you hold between your fingers
851 dthumb = 0.6 * u + 1.4 * v - 2 * 0.95
852 if dthumb > 0:
853 z -= dthumb
854 x += dthumb
855 y += dthumb
856 i = 1.0
857 alpha = 1.0
858 return (x,y,z, u,v, i, alpha)
859 def render(self, t):
860 glDisable(GL_BLEND)
861 glBindTexture(TextureTarget, Tnext)
862 DrawMeshQuad(t,lambda t, u, v: \
863 (u, v, 0.0, u, v, 1.0 - 0.5 * (1.0 - u) * (1.0 - t), 1.0))
864 EnableAlphaBlend()
865 glBindTexture(TextureTarget, Tcurrent)
866 DrawMeshQuad(t, self.warp)
867 AllTransitions.append(PageTurn)
869 ##### some additional transitions by Rob Reid <rreid@drao.nrc.ca> #####
871 class Dissipate(Transition):
872 """Sort of, but not quite, like blowing a bubble."""
873 def render(self, t):
874 glBindTexture(TextureTarget,Tcurrent)
875 scalfact = 1.0 - t
876 DrawMeshQuad(t,lambda t,u,v: (u, v, t, 0.5 + scalfact * (u - 0.5),\
877 0.5 + scalfact * (v - 0.5), 1.0, \
878 scalfact * scalfact))
879 EnableAlphaBlend()
880 glBindTexture(TextureTarget,Tnext)
881 glColor4d(1,1,1,t)
882 DrawFullQuad()
883 AllTransitions.append(Dissipate)
885 class OldOutNewIn(Transition):
886 """Parent class for transitions that do something with the current slide for
887 the first half, and then something with the next slide for the second half."""
888 def ffdmq(self, t, u, v):
889 """Function for use by DrawMeshQuad()."""
890 raise AbstractError
891 def render(self, t):
892 glColor3d(0, 0, 0)
893 DrawFullQuad()
894 if t < 0.5:
895 glBindTexture(TextureTarget, Tcurrent)
896 else:
897 glBindTexture(TextureTarget, Tnext)
898 DrawMeshQuad(t, self.ffdmq)
900 class ZoomOutIn(OldOutNewIn):
901 """Zooms the current page out, and the next one in."""
902 def ffdmq(self, t, u, v):
903 scalfact = abs(1.0 - 2.0 * t)
904 return (0.5 + scalfact * (u - 0.5), 0.5 + scalfact * (v - 0.5), 0.0, \
905 u, v, 1.0, 1.0)
906 AllTransitions.append(ZoomOutIn)
908 class SpinOutIn(OldOutNewIn):
909 """Spins the current page out, and the next one in. Inspired by a classic
910 1960's TV show."""
911 def ffdmq(self, t, u, v):
912 scalfact = abs(1.0 - 2.0 * t)
913 sa = scalfact * sin(16.0 * t)
914 ca = scalfact * cos(16.0 * t)
915 return (0.5 + ca * (u - 0.5) - 0.75 * sa * (v - 0.5),\
916 0.5 + 1.333 * sa * (u - 0.5) + ca * (v - 0.5),\
917 0.0, u, v, 1.0, 1.0)
918 AllTransitions.append(SpinOutIn)
920 class SpiralOutIn(OldOutNewIn):
921 """Flushes the current page away, only to have the next one overflow!"""
922 def ffdmq(self, t, u, v):
923 scalfact = abs(1.0 - 2.0 * t)
924 sa = scalfact * sin(16.0 * t)
925 ca = scalfact * cos(16.0 * t)
926 return (0.5 + sa + ca * (u - 0.5) - 0.75 * sa * (v - 0.5),\
927 0.5 + ca + 1.333 * sa * (u - 0.5) + ca * (v - 0.5),\
928 0.0, u, v, 1.0, 1.0)
929 AllTransitions.append(SpiralOutIn)
931 class SlideCarousel(OldOutNewIn):
932 """Old school transition accidentally made while working on SpinOutIn."""
933 def ffdmq(self, t, u, v):
934 sa = sin(6.0 * t)
935 ca = cos(6.0 * t)
936 scalfact = 0.5 + 0.5 * abs(1.0 - 2.0 * t)
937 const = 0.5 - 0.5 * scalfact
938 return (ca * (const + scalfact * u) - sa * (const + scalfact * v), \
939 sa * (const + scalfact * u) + ca * (const + scalfact * v), \
940 0.0, u, v, 1.0, 1.0)
941 AllTransitions.append(SlideCarousel)
943 class FlipBoardVert(OldOutNewIn):
944 """Should look something like the spinning bookcase to the secret room..."""
945 def ffdmq(self, t, u, v):
946 pit = 3.1415626 * t
947 c = cos(pit)
948 s = sin(pit)
949 if t < 0.5:
950 x = 0.5 + (u - 0.5) * c
951 z = (u - 0.5) * s
952 else:
953 x = 0.5 - (u - 0.5) * c
954 z = (0.5 - u) * s
956 return (x, v, z, u, v, 1.0, 1.0)
957 AllTransitions.append(FlipBoardVert)
959 class FlipBoardHori(OldOutNewIn):
960 """Some blackboards and whiteboards do this..."""
961 def ffdmq(self, t, u, v):
962 pit = 3.1415626 * t
963 c = cos(pit)
964 s = sin(pit)
965 if t < 0.5:
966 y = 0.5 + (v - 0.5) * c
967 z = (v - 0.5) * s
968 else:
969 y = 0.5 - (v - 0.5) * c
970 z = (0.5 - v) * s
972 return (u, y, z, u, v, 1.0, 1.0)
973 AllTransitions.append(FlipBoardHori)
975 def verticalblindold(t, u, v):
976 pit = 3.1415626 * t
977 umuaxis = (u % 0.2) - 0.1
978 uaxis = u - umuaxis
979 return (uaxis + umuaxis * cos(pit), v, umuaxis * sin(pit), u, v, 1.0, 1.0)
981 def verticalblindnew(t, u, v):
982 vmvaxis = (v % 0.2) - 0.1
983 vaxis = v - vmvaxis
984 return (u, vaxis - vmvaxis * cos(3.1415626 * t),\
985 -vmvaxis * sin(3.1415626 * t), u, v, 1.0, 1.0)
987 class VerticalBlinds(Transition):
988 """Vertical Venetian Blinds"""
989 def render(self, t):
990 glColor3d(0,0,0)
991 DrawFullQuad()
992 if t < 0.5:
993 glBindTexture(TextureTarget,Tcurrent)
994 DrawMeshQuad(t, verticalblindold)
995 else:
996 glBindTexture(TextureTarget,Tnext)
997 DrawMeshQuad(1.0 - t, verticalblindold)
998 AllTransitions.append(VerticalBlinds)
1000 # the AvailableTransitions array contains a list of all transition classes that
1001 # can be randomly assigned to pages
1002 AvailableTransitions=[ # from coolest to lamest
1003 # PagePeel, # deactivated: too intrusive
1004 WipeBlobs,
1005 WipeCenterOut,WipeCenterIn,
1006 WipeDownRight,WipeUpLeft,WipeDown,WipeUp,WipeRight,WipeLeft,
1007 Crossfade
1011 ##### OSD FONT RENDERER ########################################################
1013 # force a string or sequence of ordinals into a unicode string
1014 def ForceUnicode(s, charset='iso8859-15'):
1015 if type(s) == types.UnicodeType:
1016 return s
1017 if type(s) == types.StringType:
1018 return unicode(s, charset, 'ignore')
1019 if type(s) in (types.TupleType, types.ListType):
1020 return u''.join(map(unichr, s))
1021 raise TypeError, "string argument not convertible to Unicode"
1023 # search a system font path for a font file
1024 def SearchFont(root, name):
1025 if not os.path.isdir(root):
1026 return None
1027 infix = ""
1028 fontfile = []
1029 while (len(infix) < 10) and (len(fontfile) != 1):
1030 fontfile = filter(os.path.isfile, glob.glob(root + infix + name))
1031 infix += "*/"
1032 if len(fontfile) != 1:
1033 return None
1034 else:
1035 return fontfile[0]
1037 # load a system font
1038 def LoadFont(dirs, name, size):
1039 # first try to load the font directly
1040 try:
1041 return ImageFont.truetype(name, size, encoding='unic')
1042 except:
1043 pass
1044 # no need to search further on Windows
1045 if os.name == 'nt':
1046 return None
1047 # start search for the font
1048 for dir in dirs:
1049 fontfile = SearchFont(dir + "/", name)
1050 if fontfile:
1051 try:
1052 return ImageFont.truetype(fontfile, size, encoding='unic')
1053 except:
1054 pass
1055 return None
1057 # alignment constants
1058 Left = 0
1059 Right = 1
1060 Center = 2
1061 Down = 0
1062 Up = 1
1063 Auto = -1
1065 # font renderer class
1066 class GLFont:
1067 def __init__(self, width, height, name, size, search_path=[], default_charset='iso8859-15', extend=1, blur=1):
1068 self.width = width
1069 self.height = height
1070 self._i_extend = range(extend)
1071 self._i_blur = range(blur)
1072 self.feather = extend + blur + 1
1073 self.current_x = 0
1074 self.current_y = 0
1075 self.max_height = 0
1076 self.boxes = {}
1077 self.widths = {}
1078 self.line_height = 0
1079 self.default_charset = default_charset
1080 if type(name) == types.StringType:
1081 self.font = LoadFont(search_path, name, size)
1082 else:
1083 for check_name in name:
1084 self.font = LoadFont(search_path, check_name, size)
1085 if self.font: break
1086 if not self.font:
1087 raise IOError, "font file not found"
1088 self.img = Image.new('LA', (width, height))
1089 self.alpha = Image.new('L', (width, height))
1090 self.extend = ImageFilter.MaxFilter()
1091 self.blur = ImageFilter.Kernel((3, 3), [1,2,1,2,4,2,1,2,1])
1092 self.tex = glGenTextures(1)
1093 glBindTexture(GL_TEXTURE_2D, self.tex)
1094 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
1095 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
1096 self.AddString(range(32, 128))
1098 def AddCharacter(self, c):
1099 w, h = self.font.getsize(c)
1100 self.line_height = max(self.line_height, h)
1101 size = (w + 2 * self.feather, h + 2 * self.feather)
1102 glyph = Image.new('L', size)
1103 draw = ImageDraw.Draw(glyph)
1104 draw.text((self.feather, self.feather), c, font=self.font, fill=255)
1105 del draw
1107 box = self.AllocateGlyphBox(*size)
1108 self.img.paste(glyph, (box.orig_x, box.orig_y))
1110 for i in self._i_extend: glyph = glyph.filter(self.extend)
1111 for i in self._i_blur: glyph = glyph.filter(self.blur)
1112 self.alpha.paste(glyph, (box.orig_x, box.orig_y))
1114 self.boxes[c] = box
1115 self.widths[c] = w
1116 del glyph
1118 def AddString(self, s, charset=None, fail_silently=False):
1119 update_count = 0
1120 try:
1121 for c in ForceUnicode(s, self.GetCharset(charset)):
1122 if c in self.widths:
1123 continue
1124 self.AddCharacter(c)
1125 update_count += 1
1126 except ValueError:
1127 if fail_silently:
1128 pass
1129 else:
1130 raise
1131 if not update_count: return
1132 self.img.putalpha(self.alpha)
1133 glBindTexture(GL_TEXTURE_2D, self.tex)
1134 glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE_ALPHA, \
1135 self.width, self.height, 0, \
1136 GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, self.img.tostring())
1138 def AllocateGlyphBox(self, w, h):
1139 if self.current_x + w > self.width:
1140 self.current_x = 0
1141 self.current_y += self.max_height
1142 self.max_height = 0
1143 if self.current_y + h > self.height:
1144 raise ValueError, "bitmap too small for all the glyphs"
1145 box = self.GlyphBox()
1146 box.orig_x = self.current_x
1147 box.orig_y = self.current_y
1148 box.size_x = w
1149 box.size_y = h
1150 box.x0 = self.current_x / float(self.width)
1151 box.y0 = self.current_y / float(self.height)
1152 box.x1 = (self.current_x + w) / float(self.width)
1153 box.y1 = (self.current_y + h) / float(self.height)
1154 box.dsx = w * PixelX
1155 box.dsy = h * PixelY
1156 self.current_x += w
1157 self.max_height = max(self.max_height, h)
1158 return box
1160 def GetCharset(self, charset=None):
1161 if charset: return charset
1162 return self.default_charset
1164 def SplitText(self, s, charset=None):
1165 return ForceUnicode(s, self.GetCharset(charset)).split(u'\n')
1167 def GetLineHeight(self):
1168 return self.line_height
1170 def GetTextWidth(self, s, charset=None):
1171 return max([self.GetTextWidthEx(line) for line in self.SplitText(s, charset)])
1173 def GetTextHeight(self, s, charset=None):
1174 return len(self.SplitText(s, charset)) * self.line_height
1176 def GetTextSize(self, s, charset=None):
1177 lines = self.SplitText(s, charset)
1178 return (max([self.GetTextWidthEx(line) for line in lines]), len(lines) * self.line_height)
1180 def GetTextWidthEx(self, u):
1181 if u: return sum([self.widths.get(c, 0) for c in u])
1182 else: return 0
1184 def GetTextHeightEx(self, u=[]):
1185 return self.line_height
1187 def AlignTextEx(self, x, u, align=Left):
1188 if not align: return x
1189 return x - (self.GetTextWidthEx(u) / align)
1191 def Draw(self, origin, text, charset=None, align=Left, color=(1.0, 1.0, 1.0), alpha=1.0, beveled=True):
1192 lines = self.SplitText(text, charset)
1193 x0, y0 = origin
1194 x0 -= self.feather
1195 y0 -= self.feather
1196 glEnable(GL_TEXTURE_2D)
1197 glEnable(GL_BLEND)
1198 glBindTexture(GL_TEXTURE_2D, self.tex)
1199 if beveled:
1200 glBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_ALPHA)
1201 glColor4d(0.0, 0.0, 0.0, alpha)
1202 self.DrawLinesEx(x0, y0, lines, align)
1203 glBlendFunc(GL_ONE, GL_ONE)
1204 glColor3d(color[0] * alpha, color[1] * alpha, color[2] * alpha)
1205 self.DrawLinesEx(x0, y0, lines, align)
1206 glDisable(GL_BLEND)
1207 glDisable(GL_TEXTURE_2D)
1209 def DrawLinesEx(self, x0, y, lines, align=Left):
1210 global PixelX, PixelY
1211 glBegin(GL_QUADS)
1212 for line in lines:
1213 sy = y * PixelY
1214 x = self.AlignTextEx(x0, line, align)
1215 for c in line:
1216 if not c in self.widths: continue
1217 self.boxes[c].render(x * PixelX, sy)
1218 x += self.widths[c]
1219 y += self.line_height
1220 glEnd()
1222 class GlyphBox:
1223 def render(self, sx=0.0, sy=0.0):
1224 glTexCoord2d(self.x0, self.y0); glVertex2d(sx, sy)
1225 glTexCoord2d(self.x0, self.y1); glVertex2d(sx, sy+self.dsy)
1226 glTexCoord2d(self.x1, self.y1); glVertex2d(sx+self.dsx, sy+self.dsy)
1227 glTexCoord2d(self.x1, self.y0); glVertex2d(sx+self.dsx, sy)
1229 # high-level draw function
1230 def DrawOSD(x, y, text, halign=Auto, valign=Auto, alpha=1.0):
1231 if not(OSDFont) or not(text) or (alpha <= 0.004): return
1232 if alpha > 1.0: alpha = 1.0
1233 if halign == Auto:
1234 if x < 0:
1235 x += ScreenWidth
1236 halign = Right
1237 else:
1238 halign = Left
1239 if valign == Auto:
1240 if y < 0:
1241 y += ScreenHeight
1242 valign = Up
1243 else:
1244 valign = Down
1245 if valign != Down:
1246 y -= OSDFont.GetLineHeight() / valign
1247 if TextureTarget != GL_TEXTURE_2D:
1248 glDisable(TextureTarget)
1249 OSDFont.Draw((x, y), text, align=halign, alpha=alpha)
1251 # very high-level draw function
1252 def DrawOSDEx(position, text, alpha_factor=1.0):
1253 xpos = position >> 1
1254 y = (1 - 2 * (position & 1)) * OSDMargin
1255 if xpos < 2:
1256 x = (1 - 2 * xpos) * OSDMargin
1257 halign = Auto
1258 else:
1259 x = ScreenWidth / 2
1260 halign = Center
1261 DrawOSD(x, y, text, halign, alpha = OSDAlpha * alpha_factor)
1264 ##### PDF PARSER ###############################################################
1266 class PDFError(Exception):
1267 pass
1269 class PDFref:
1270 def __init__(self, ref):
1271 self.ref = ref
1272 def __repr__(self):
1273 return "PDFref(%d)" % self.ref
1275 re_pdfstring = re.compile(r'\(\)|\(.*?[^\\]\)')
1276 pdfstringrepl = [("\\"+x[0], x[1:]) for x in "(( )) n\n r\r t\t".split(" ")]
1277 def pdf_maskstring(s):
1278 s = s[1:-1]
1279 for a, b in pdfstringrepl:
1280 s = s.replace(a, b)
1281 return " <" + "".join(["%02X"%ord(c) for c in s]) + "> "
1282 def pdf_mask_all_strings(s):
1283 return re_pdfstring.sub(lambda x: pdf_maskstring(x.group(0)), s)
1284 def pdf_unmaskstring(s):
1285 return "".join([chr(int(s[i:i+2], 16)) for i in xrange(1, len(s)-1, 2)])
1287 class PDFParser:
1288 def __init__(self, filename):
1289 self.f = file(filename, "rb")
1291 # find the first cross-reference table
1292 self.f.seek(0, 2)
1293 filesize = self.f.tell()
1294 self.f.seek(filesize - 128)
1295 trailer = self.f.read()
1296 i = trailer.rfind("startxref")
1297 if i < 0:
1298 raise PDFError, "cross-reference table offset missing"
1299 try:
1300 offset = int(trailer[i:].split("\n")[1].strip())
1301 except (IndexError, ValueError):
1302 raise PDFError, "malformed cross-reference table offset"
1304 # follow the trailer chain
1305 self.xref = {}
1306 while offset:
1307 newxref = self.xref
1308 self.xref, rootref, offset = self.parse_trailer(offset)
1309 self.xref.update(newxref)
1311 # scan the page tree
1312 self.obj2page = {}
1313 self.page2obj = {}
1314 self.annots = {}
1315 self.page_count = 0
1316 self.box = {}
1317 root = self.getobj(rootref, 'Catalog')
1318 try:
1319 self.scan_page_tree(root['Pages'].ref)
1320 except KeyError:
1321 raise PDFError, "root page tree node missing"
1323 def getline(self):
1324 while True:
1325 line = self.f.readline().strip()
1326 if line: return line
1328 def find_length(self, tokens, begin, end):
1329 level = 1
1330 for i in xrange(1, len(tokens)):
1331 if tokens[i] == begin: level += 1
1332 if tokens[i] == end: level -= 1
1333 if not level: break
1334 return i + 1
1336 def parse_tokens(self, tokens, want_list=False):
1337 res = []
1338 while tokens:
1339 t = tokens[0]
1340 v = t
1341 tlen = 1
1342 if (len(tokens) >= 3) and (tokens[2] == 'R'):
1343 v = PDFref(int(t))
1344 tlen = 3
1345 elif t == "<<":
1346 tlen = self.find_length(tokens, "<<", ">>")
1347 v = self.parse_tokens(tokens[1 : tlen - 1], True)
1348 v = dict(zip(v[::2], v[1::2]))
1349 elif t == "[":
1350 tlen = self.find_length(tokens, "[", "]")
1351 v = self.parse_tokens(tokens[1 : tlen - 1], True)
1352 elif not(t) or (t[0] == "null"):
1353 v = None
1354 elif (t[0] == '<') and (t[-1] == '>'):
1355 v = pdf_unmaskstring(t)
1356 elif t[0] == '/':
1357 v = t[1:]
1358 elif t == 'null':
1359 v = None
1360 else:
1361 try:
1362 v = float(t)
1363 v = int(t)
1364 except ValueError:
1365 pass
1366 res.append(v)
1367 del tokens[:tlen]
1368 if want_list:
1369 return res
1370 if not res:
1371 return None
1372 if len(res) == 1:
1373 return res[0]
1374 return res
1376 def parse(self, data):
1377 data = pdf_mask_all_strings(data)
1378 data = data.replace("<<", " << ").replace("[", " [ ").replace("(", " (")
1379 data = data.replace(">>", " >> ").replace("]", " ] ").replace(")", ") ")
1380 data = data.replace("/", " /")
1381 return self.parse_tokens(filter(None, data.split()))
1383 def getobj(self, obj, force_type=None):
1384 offset = self.xref.get(obj, 0)
1385 if not offset:
1386 raise PDFError, "referenced non-existing PDF object"
1387 self.f.seek(offset)
1388 header = self.getline().split(None, 2)
1389 if (header[-1] != "obj") or (header[0] != str(obj)):
1390 raise PDFError, "object does not start where it's supposed to"
1391 data = []
1392 while True:
1393 line = self.getline()
1394 if line in ("endobj", "stream"): break
1395 data.append(line)
1396 data = self.parse(" ".join(data))
1397 if force_type:
1398 try:
1399 t = data['Type']
1400 except (KeyError, IndexError, ValueError):
1401 t = None
1402 if t != force_type:
1403 raise PDFError, "object does not match the intended type"
1404 return data
1406 def parse_xref_section(self, start, count):
1407 xref = {}
1408 for obj in xrange(start, start + count):
1409 line = self.getline()
1410 if line[-1] == 'f':
1411 xref[obj] = 0
1412 else:
1413 xref[obj] = int(line[:10], 10)
1414 return xref
1416 def parse_trailer(self, offset):
1417 self.f.seek(offset)
1418 xref = {}
1419 rootref = 0
1420 offset = 0
1421 if self.getline() != "xref":
1422 raise PDFError, "cross-reference table does not start where it's supposed to"
1423 return (xref, rootref, offset) # no xref table found, abort
1424 # parse xref sections
1425 while True:
1426 line = self.getline()
1427 if line == "trailer": break
1428 start, count = map(int, line.split())
1429 xref.update(self.parse_xref_section(start, count))
1430 # parse trailer
1431 while True:
1432 line = self.getline()
1433 if line in ("startxref", "%%EOF"): break
1434 if line[0] != '/': continue
1435 parts = line[1:].split()
1436 if parts[0] == 'Prev':
1437 offset = int(parts[1])
1438 if parts[0] == 'Root':
1439 if (len(parts) != 4) or (parts[3] != 'R'):
1440 raise PDFError, "root catalog entry is not a reference"
1441 rootref = int(parts[1])
1442 return (xref, rootref, offset)
1444 def scan_page_tree(self, obj, mbox=None, cbox=None):
1445 node = self.getobj(obj)
1446 if node['Type'] == 'Pages':
1447 for kid in node['Kids']:
1448 self.scan_page_tree(kid.ref, node.get('MediaBox', mbox), node.get('CropBox', cbox))
1449 else:
1450 page = self.page_count + 1
1451 self.page_count = page
1452 self.obj2page[obj] = page
1453 self.page2obj[page] = obj
1454 self.annots[page] = [a.ref for a in node.get('Annots', [])]
1455 self.box[page] = node.get('CropBox', cbox) or node.get('MediaBox', mbox)
1457 def dest2page(self, dest):
1458 if type(dest) != types.ListType:
1459 return dest
1460 elif dest[0].__class__ == PDFref:
1461 return self.obj2page.get(dest[0].ref, None)
1462 else:
1463 return dest[0]
1465 def get_href(self, obj):
1466 node = self.getobj(obj, 'Annot')
1467 if node['Subtype'] != 'Link': return None
1468 dest = None
1469 if 'Dest' in node:
1470 dest = self.dest2page(node['Dest'])
1471 elif 'A' in node:
1472 action = node['A']['S']
1473 if action == 'URI':
1474 dest = node['A'].get('URI', None)
1475 elif action == 'GoTo':
1476 dest = self.dest2page(node['A'].get('D', None))
1477 if dest:
1478 return tuple(node['Rect'] + [dest])
1480 def GetHyperlinks(self):
1481 res = {}
1482 for page in self.annots:
1483 a = filter(None, map(self.get_href, self.annots[page]))
1484 if a: res[page] = a
1485 return res
1488 def AddHyperlink(page_offset, page, target, linkbox, pagebox):
1489 global PowerdotFix
1490 page += page_offset
1491 if type(target) == types.IntType:
1492 target += page_offset
1493 w = 1.0 / (pagebox[2] - pagebox[0])
1494 h = 1.0 / (pagebox[3] - pagebox[1])
1496 # Hack to fix problems with landscape/powerdot files with hyperlinks
1497 if(PowerdotFix is True):
1498 y1 = linkbox[0] * w
1499 x1 = linkbox[3] * h
1500 y0 = linkbox[2] * w
1501 x0 = linkbox[1] * h
1502 else:
1503 x0 = (linkbox[0] - pagebox[0]) * w
1504 y0 = (pagebox[3] - linkbox[3]) * h
1505 x1 = (linkbox[2] - pagebox[0]) * w
1506 y1 = (pagebox[3] - linkbox[1]) * h
1508 href = (0, target, x0, y0, x1, y1)
1509 if GetPageProp(page, '_href'):
1510 PageProps[page]['_href'].append(href)
1511 else:
1512 SetPageProp(page, '_href', [href])
1515 def FixHyperlinks(page):
1516 if not(GetPageProp(page, '_box')) or not(GetPageProp(page, '_href')):
1517 return # no hyperlinks or unknown page size
1518 bx0, by0, bx1, by1 = GetPageProp(page, '_box')
1519 bdx = bx1 - bx0
1520 bdy = by1 - by0
1521 href = []
1522 for fixed, target, x0, y0, x1, y1 in GetPageProp(page, '_href'):
1523 if fixed:
1524 href.append((1, target, x0, y0, x1, y1))
1525 else:
1526 href.append((1, target, \
1527 int(bx0 + bdx * x0), int(by0 + bdy * y0), \
1528 int(bx0 + bdx * x1), int(by0 + bdy * y1)))
1529 SetPageProp(page, '_href', href)
1532 def ParsePDF(filename):
1533 try:
1534 assert 0 == spawn(os.P_WAIT, pdftkPath, \
1535 ["pdftk", FileNameEscape + filename + FileNameEscape, \
1536 "output", FileNameEscape + TempFileName + ".pdf" + FileNameEscape,
1537 "uncompress"])
1538 except OSError:
1539 print >>sys.stderr, "Note: pdftk not found, hyperlinks disabled."
1540 return
1541 except AssertionError:
1542 print >>sys.stderr, "Note: pdftk failed, hyperlinks disabled."
1543 return
1545 count = 0
1546 try:
1547 try:
1548 pdf = PDFParser(TempFileName + ".pdf")
1549 for page, annots in pdf.GetHyperlinks().iteritems():
1550 for page_offset in FileProps[filename]['offsets']:
1551 for a in annots:
1552 AddHyperlink(page_offset, page, a[4], a[:4], pdf.box[page])
1553 count += len(annots)
1554 FixHyperlinks(page)
1555 del pdf
1556 return count
1557 except IOError:
1558 print >>sys.stderr, "Note: file produced by pdftk not readable, hyperlinks disabled."
1559 except PDFError, e:
1560 print >>sys.stderr, "Note: error in file produced by pdftk, hyperlinks disabled."
1561 print >>sys.stderr, " PDF parser error message:", e
1562 finally:
1563 try:
1564 os.remove(TempFileName + ".pdf")
1565 except OSError:
1566 pass
1569 ##### PAGE CACHE MANAGEMENT ####################################################
1571 # helper class that allows PIL to write and read image files with an offset
1572 class IOWrapper:
1573 def __init__(self, f, offset=0):
1574 self.f = f
1575 self.offset = offset
1576 self.f.seek(offset)
1577 def read(self, count=None):
1578 if count is None:
1579 return self.f.read()
1580 else:
1581 return self.f.read(count)
1582 def write(self, data):
1583 self.f.write(data)
1584 def seek(self, pos, whence=0):
1585 assert(whence in (0, 1))
1586 if whence:
1587 self.f.seek(pos, 1)
1588 else:
1589 self.f.seek(pos + self.offset)
1590 def tell(self):
1591 return self.f.tell() - self.offset
1593 # generate a "magic number" that is used to identify persistent cache files
1594 def UpdateCacheMagic():
1595 global CacheMagic
1596 pool = [PageCount, ScreenWidth, ScreenHeight, b2s(Scaling), b2s(Supersample), b2s(Rotation)]
1597 flist = list(FileProps.keys())
1598 flist.sort(lambda a,b: cmp(a.lower(), b.lower()))
1599 for f in flist:
1600 pool.append(f)
1601 pool.extend(list(GetFileProp(f, 'stat', [])))
1602 CacheMagic = md5.new("\0".join(map(str, pool))).hexdigest()
1604 # set the persistent cache file position to the current end of the file
1605 def UpdatePCachePos():
1606 global CacheFilePos
1607 CacheFile.seek(0, 2)
1608 CacheFilePos = CacheFile.tell()
1610 # rewrite the header of the persistent cache
1611 def WritePCacheHeader(reset=False):
1612 pages = ["%08x" % PageCache.get(page, 0) for page in range(1, PageCount+1)]
1613 CacheFile.seek(0)
1614 CacheFile.write(CacheMagic + "".join(pages))
1615 if reset:
1616 CacheFile.truncate()
1617 UpdatePCachePos()
1619 # return an image from the persistent cache or None if none is available
1620 def GetPCacheImage(page):
1621 if CacheMode != PersistentCache:
1622 return # not applicable if persistent cache isn't used
1623 Lcache.acquire()
1624 try:
1625 if page in PageCache:
1626 img = Image.open(IOWrapper(CacheFile, PageCache[page]))
1627 img.load()
1628 return img
1629 finally:
1630 Lcache.release()
1632 # returns an image from the non-persistent cache or None if none is available
1633 def GetCacheImage(page):
1634 if CacheMode in (NoCache, PersistentCache):
1635 return # not applicable in uncached or persistent-cache mode
1636 Lcache.acquire()
1637 try:
1638 if page in PageCache:
1639 if CacheMode == FileCache:
1640 CacheFile.seek(PageCache[page])
1641 return CacheFile.read(TexSize)
1642 else:
1643 return PageCache[page]
1644 finally:
1645 Lcache.release()
1647 # adds an image to the persistent cache
1648 def AddToPCache(page, img):
1649 if CacheMode != PersistentCache:
1650 return # not applicable if persistent cache isn't used
1651 Lcache.acquire()
1652 try:
1653 if page in PageCache:
1654 return # page is already cached and we can't update it safely
1655 # -> stop here (the new image will be identical to the old
1656 # one anyway)
1657 img.save(IOWrapper(CacheFile, CacheFilePos), "ppm")
1658 PageCache[page] = CacheFilePos
1659 WritePCacheHeader()
1660 finally:
1661 Lcache.release()
1663 # adds an image to the non-persistent cache
1664 def AddToCache(page, data):
1665 global CacheFilePos
1666 if CacheMode in (NoCache, PersistentCache):
1667 return # not applicable in uncached or persistent-cache mode
1668 Lcache.acquire()
1669 try:
1670 if CacheMode == FileCache:
1671 if not(page in PageCache):
1672 PageCache[page] = CacheFilePos
1673 CacheFilePos += len(data)
1674 CacheFile.seek(PageCache[page])
1675 CacheFile.write(data)
1676 else:
1677 PageCache[page] = data
1678 finally:
1679 Lcache.release()
1681 # invalidates the whole cache
1682 def InvalidateCache():
1683 global PageCache, CacheFilePos
1684 Lcache.acquire()
1685 try:
1686 PageCache = {}
1687 if CacheMode == PersistentCache:
1688 UpdateCacheMagic()
1689 WritePCacheHeader(True)
1690 else:
1691 CacheFilePos = 0
1692 finally:
1693 Lcache.release()
1695 # initialize the persistent cache
1696 def InitPCache():
1697 global CacheFile, CacheMode
1699 # try to open the pre-existing cache file
1700 try:
1701 CacheFile = file(CacheFileName, "rb+")
1702 except IOError:
1703 CacheFile = None
1705 # check the cache magic
1706 UpdateCacheMagic()
1707 if CacheFile and (CacheFile.read(32) != CacheMagic):
1708 print >>sys.stderr, "Cache file mismatch, recreating cache."
1709 CacheFile.close()
1710 CacheFile = None
1712 if CacheFile:
1713 # if the magic was valid, import cache data
1714 print >>sys.stderr, "Using already existing persistent cache file."
1715 for page in range(1, PageCount+1):
1716 offset = int(CacheFile.read(8), 16)
1717 if offset:
1718 PageCache[page] = offset
1719 UpdatePCachePos()
1720 else:
1721 # if the magic was invalid or the file didn't exist, (re-)create it
1722 try:
1723 CacheFile = file(CacheFileName, "wb+")
1724 except IOError:
1725 print >>sys.stderr, "Error: cannot write the persistent cache file (`%s')" % CacheFileName
1726 print >>sys.stderr, "Falling back to temporary file cache."
1727 CacheMode = FileCache
1728 WritePCacheHeader()
1731 ##### PAGE RENDERING ###########################################################
1733 # generate a dummy image
1734 def DummyPage():
1735 img = Image.new('RGB', (ScreenWidth, ScreenHeight))
1736 img.paste(LogoImage, ((ScreenWidth - LogoImage.size[0]) / 2,
1737 (ScreenHeight - LogoImage.size[1]) / 2))
1738 return img
1740 # load a page from a PDF file
1741 def RenderPDF(page, MayAdjustResolution, ZoomMode):
1742 global UseGhostScript
1743 UseGhostScriptOnce = False
1745 SourceFile = GetPageProp(page, '_file')
1746 Resolution = GetFileProp(SourceFile, 'res', 96)
1747 RealPage = GetPageProp(page, '_page')
1749 if Supersample and not(ZoomMode):
1750 UseRes = int(0.5 + Resolution) * Supersample
1751 AlphaBits = 1
1752 else:
1753 UseRes = int(0.5 + Resolution)
1754 AlphaBits = 4
1755 if ZoomMode:
1756 UseRes = 2 * UseRes
1758 # call pdftoppm to generate the page image
1759 if not UseGhostScript:
1760 renderer = "pdftoppm"
1761 try:
1762 assert 0 == spawn(os.P_WAIT, \
1763 pdftoppmPath, ["pdftoppm", "-q"] + [ \
1764 "-f", str(RealPage), "-l", str(RealPage),
1765 "-r", str(int(UseRes)),
1766 FileNameEscape + SourceFile + FileNameEscape,
1767 TempFileName])
1768 # determine output filename
1769 digits = GetFileProp(SourceFile, 'digits', 6)
1770 imgfile = TempFileName + ("-%%0%dd.ppm" % digits) % RealPage
1771 if not os.path.exists(imgfile):
1772 for digits in xrange(6, 0, -1):
1773 imgfile = TempFileName + ("-%%0%dd.ppm" % digits) % RealPage
1774 if os.path.exists(imgfile): break
1775 SetFileProp(SourceFile, 'digits', digits)
1776 except OSError, (errcode, errmsg):
1777 print >>sys.stderr, "Warning: Cannot start pdftoppm -", errmsg
1778 print >>sys.stderr, "Falling back to GhostScript (permanently)."
1779 UseGhostScript = True
1780 except AssertionError:
1781 print >>sys.stderr, "There was an error while rendering page %d" % page
1782 print >>sys.stderr, "Falling back to GhostScript for this page."
1783 UseGhostScriptOnce = True
1785 # fallback to GhostScript
1786 if UseGhostScript or UseGhostScriptOnce:
1787 imgfile = TempFileName + ".tif"
1788 renderer = "GhostScript"
1789 try:
1790 assert 0 == spawn(os.P_WAIT, \
1791 GhostScriptPath, ["gs", "-q"] + GhostScriptPlatformOptions + [ \
1792 "-dBATCH", "-dNOPAUSE", "-sDEVICE=tiff24nc", "-dUseCropBox",
1793 "-sOutputFile=" + imgfile, \
1794 "-dFirstPage=%d" % RealPage, "-dLastPage=%d" % RealPage,
1795 "-r%dx%d" % (UseRes, int(UseRes * PAR)), \
1796 "-dTextAlphaBits=%d" % AlphaBits, \
1797 "-dGraphicsAlphaBits=%s" % AlphaBits, \
1798 FileNameEscape + SourceFile + FileNameEscape])
1799 except OSError, (errcode, errmsg):
1800 print >>sys.stderr, "Error: Cannot start GhostScript -", errmsg
1801 return DummyPage()
1802 except AssertionError:
1803 print >>sys.stderr, "There was an error while rendering page %d" % page
1804 return DummyPage()
1806 # open the page image file with PIL
1807 try:
1808 img = Image.open(imgfile)
1809 except:
1810 print >>sys.stderr, "Error: %s produced an unreadable file (page %d)" % (renderer, page)
1811 return DummyPage()
1813 # try to delete the file again (this constantly fails on Win32 ...)
1814 try:
1815 os.remove(imgfile)
1816 except OSError:
1817 pass
1819 # apply rotation
1820 rot = GetPageProp(page, 'rotate')
1821 if rot is None:
1822 rot = Rotation
1823 if rot:
1824 img = img.rotate(90 * (4 - rot))
1826 # determine real display size (don't care for ZoomMode, DisplayWidth and
1827 # DisplayHeight are only used for Supersample and AdjustResolution anyway)
1828 if Supersample:
1829 DisplayWidth = img.size[0] / Supersample
1830 DisplayHeight = img.size[1] / Supersample
1831 else:
1832 DisplayWidth = img.size[0]
1833 DisplayHeight = img.size[1]
1835 # if the image size is strange, re-adjust the rendering resolution
1836 if MayAdjustResolution \
1837 and ((abs(ScreenWidth - DisplayWidth) > 4) \
1838 or (abs(ScreenHeight - DisplayHeight) > 4)):
1839 newsize = ZoomToFit((DisplayWidth,DisplayHeight))
1840 NewResolution = newsize[0] * Resolution/DisplayWidth
1841 if abs(1.0 - NewResolution / Resolution) > 0.05:
1842 # only modify anything if the resolution deviation is large enough
1843 SetFileProp(SourceFile, 'res', NewResolution)
1844 return RenderPDF(page, False, ZoomMode)
1846 # downsample a supersampled image
1847 if Supersample and not(ZoomMode):
1848 return img.resize((DisplayWidth, DisplayHeight), Image.ANTIALIAS)
1850 return img
1853 # load a page from an image file
1854 def LoadImage(page, ZoomMode):
1855 # open the image file with PIL
1856 try:
1857 img = Image.open(GetPageProp(page, '_file'))
1858 except:
1859 print >>sys.stderr, "Image file `%s' is broken." % (FileList[page - 1])
1860 return DummyPage()
1862 # apply rotation
1863 rot = GetPageProp(page, 'rotate')
1864 if rot is None:
1865 rot = Rotation
1866 if rot:
1867 img = img.rotate(90 * (4 - rot))
1869 # determine destination size
1870 newsize = ZoomToFit(img.size)
1871 # don't scale if the source size is too close to the destination size
1872 if abs(newsize[0] - img.size[0]) < 2: newsize = img.size
1873 # don't scale if the source is smaller than the destination
1874 if not(Scaling) and (newsize > img.size): newsize = img.size
1875 # zoom up (if wanted)
1876 if ZoomMode: newsize=(2 * newsize[0], 2 * newsize[1])
1877 # skip processing if there was no change
1878 if newsize == img.size: return img
1880 # select a nice filter and resize the image
1881 if newsize > img.size:
1882 filter = Image.BICUBIC
1883 else:
1884 filter = Image.ANTIALIAS
1885 return img.resize(newsize, filter)
1888 # render a page to an OpenGL texture
1889 def PageImage(page, ZoomMode=False, RenderMode=False):
1890 global OverviewNeedUpdate
1891 EnableCacheRead = not(ZoomMode or RenderMode)
1892 EnableCacheWrite = EnableCacheRead and \
1893 (page >= PageRangeStart) and (page <= PageRangeEnd)
1895 # check for the image in the cache
1896 if EnableCacheRead:
1897 data = GetCacheImage(page)
1898 if data: return data
1900 # if it's not in the temporary cache, render it
1901 Lrender.acquire()
1902 try:
1903 # retrieve the image from the persistent cache or fully re-render it
1904 if EnableCacheRead:
1905 img = GetPCacheImage(page)
1906 else:
1907 img = None
1908 if not img:
1909 if GetPageProp(page, '_page'):
1910 img = RenderPDF(page, not(ZoomMode), ZoomMode)
1911 else:
1912 img = LoadImage(page, ZoomMode)
1913 if EnableCacheWrite:
1914 AddToPCache(page, img)
1916 # create black background image to paste real image onto
1917 if ZoomMode:
1918 TextureImage = Image.new('RGB', (2 * TexWidth, 2 * TexHeight))
1919 TextureImage.paste(img, ((2 * ScreenWidth - img.size[0]) / 2, \
1920 (2 * ScreenHeight - img.size[1]) / 2))
1921 else:
1922 TextureImage = Image.new('RGB', (TexWidth, TexHeight))
1923 x0 = (ScreenWidth - img.size[0]) / 2
1924 y0 = (ScreenHeight - img.size[1]) / 2
1925 TextureImage.paste(img, (x0, y0))
1926 SetPageProp(page, '_box', (x0, y0, x0 + img.size[0], y0 + img.size[1]))
1927 FixHyperlinks(page)
1929 # paste thumbnail into overview image
1930 if GetPageProp(page, ('overview', '_overview'), True) \
1931 and (page >= PageRangeStart) and (page <= PageRangeEnd) \
1932 and not(GetPageProp(page, '_overview_rendered')) \
1933 and not(RenderMode):
1934 pos = OverviewPos(OverviewPageMapInv[page])
1935 Loverview.acquire()
1936 try:
1937 # first, fill the underlying area with black (i.e. remove the dummy logo)
1938 blackness = Image.new('RGB', (OverviewCellX - OverviewBorder, \
1939 OverviewCellY - OverviewBorder))
1940 OverviewImage.paste(blackness, (pos[0] + OverviewBorder / 2, \
1941 pos[1] + OverviewBorder))
1942 del blackness
1943 # then, scale down the original image and paste it
1944 img.thumbnail((OverviewCellX - 2 * OverviewBorder, \
1945 OverviewCellY - 2 * OverviewBorder), \
1946 Image.ANTIALIAS)
1947 OverviewImage.paste(img, \
1948 (pos[0] + (OverviewCellX - img.size[0]) / 2, \
1949 pos[1] + (OverviewCellY - img.size[1]) / 2))
1950 finally:
1951 Loverview.release()
1952 SetPageProp(page, '_overview_rendered', True)
1953 OverviewNeedUpdate = True
1954 del img
1956 # return texture data
1957 if RenderMode:
1958 return TextureImage
1959 data=TextureImage.tostring()
1960 del TextureImage
1961 finally:
1962 Lrender.release()
1964 # finally add it back into the cache and return it
1965 if EnableCacheWrite:
1966 AddToCache(page, data)
1967 return data
1969 # render a page to an OpenGL texture
1970 def RenderPage(page, target):
1971 glBindTexture(TextureTarget ,target)
1972 try:
1973 glTexImage2D(TextureTarget, 0, 3, TexWidth, TexHeight, 0,\
1974 GL_RGB, GL_UNSIGNED_BYTE, PageImage(page))
1975 except GLerror:
1976 print >>sys.stderr, "I'm sorry, but your graphics card is not capable of rendering presentations"
1977 print >>sys.stderr, "in this resolution. Either the texture memory is exhausted, or there is no"
1978 print >>sys.stderr, "support for large textures (%dx%d). Please try to run Accentuate in a" % (TexWidth, TexHeight)
1979 print >>sys.stderr, "smaller resolution using the -g command-line option."
1980 sys.exit(1)
1982 # background rendering thread
1983 def RenderThread(p1, p2):
1984 global RTrunning, RTrestart
1985 RTrunning = True
1986 RTrestart = True
1987 while RTrestart:
1988 RTrestart = False
1989 for pdf in FileProps:
1990 if not pdf.lower().endswith(".pdf"): continue
1991 if RTrestart: break
1992 ParsePDF(pdf)
1993 if RTrestart: continue
1994 for page in xrange(1, PageCount + 1):
1995 if RTrestart: break
1996 if (page != p1) and (page != p2) \
1997 and (page >= PageRangeStart) and (page <= PageRangeEnd):
1998 PageImage(page)
1999 RTrunning = False
2000 if CacheMode >= FileCache:
2001 print >>sys.stderr, "Background rendering finished, used %.1f MiB of disk space." %\
2002 (CacheFilePos / 1048576.0)
2005 ##### RENDER MODE ##############################################################
2007 def DoRender():
2008 global TexWidth, TexHeight
2009 TexWidth = ScreenWidth
2010 TexHeight = ScreenHeight
2011 if os.path.exists(RenderToDirectory):
2012 print >>sys.stderr, "Destination directory `%s' already exists," % RenderToDirectory
2013 print >>sys.stderr, "refusing to overwrite anything."
2014 return 1
2015 try:
2016 os.mkdir(RenderToDirectory)
2017 except OSError, e:
2018 print >>sys.stderr, "Cannot create destination directory `%s':" % RenderToDirectory
2019 print >>sys.stderr, e.strerror
2020 return 1
2021 print >>sys.stderr, "Rendering presentation into `%s'" % RenderToDirectory
2022 for page in xrange(1, PageCount + 1):
2023 PageImage(page, RenderMode=True).save("%s/page%04d.png" % (RenderToDirectory, page))
2024 sys.stdout.write("[%d] " % page)
2025 sys.stdout.flush()
2026 print >>sys.stderr
2027 print >>sys.stderr, "Done."
2028 return 0
2031 ##### INFO SCRIPT I/O ##########################################################
2033 # info script reader
2034 def LoadInfoScript():
2035 global PageProps
2036 try:
2037 OldPageProps = PageProps
2038 execfile(InfoScriptPath, globals())
2039 NewPageProps = PageProps
2040 PageProps = OldPageProps
2041 del OldPageProps
2042 for page in NewPageProps:
2043 for prop in NewPageProps[page]:
2044 SetPageProp(page, prop, NewPageProps[page][prop])
2045 del NewPageProps
2046 except IOError:
2047 pass
2048 except:
2049 print >>sys.stderr, "----- Exception in info script ----"
2050 traceback.print_exc(file=sys.stderr)
2051 print >>sys.stderr, "----- End of traceback -----"
2053 # we can't save lamba expressions, so we need to warn the user
2054 # in every possible way
2055 ScriptTainted = False
2056 LambdaWarning = False
2057 def here_was_a_lambda_expression_that_could_not_be_saved():
2058 global LambdaWarning
2059 if not LambdaWarning:
2060 print >>sys.stderr, "WARNING: The info script for the current file contained lambda expressions that"
2061 print >>sys.stderr, " were removed during the a save operation."
2062 LambdaWarning = True
2064 # "clean" a PageProps entry so that only 'public' properties are left
2065 def GetPublicProps(props):
2066 props = props.copy()
2067 # delete private (underscore) props
2068 for prop in list(props.keys()):
2069 if str(prop)[0] == '_':
2070 del props[prop]
2071 # clean props to default values
2072 if props.get('overview', False):
2073 del props['overview']
2074 if not props.get('skip', True):
2075 del props['skip']
2076 if ('boxes' in props) and not(props['boxes']):
2077 del props['boxes']
2078 return props
2080 # Generate a string representation of a property value. Mainly this converts
2081 # classes or instances to the name of the class.
2082 def PropValueRepr(value):
2083 global ScriptTainted
2084 if type(value) == types.FunctionType:
2085 if value.__name__ != "<lambda>":
2086 return value.__name__
2087 if not ScriptTainted:
2088 print >>sys.stderr, "WARNING: The info script contains lambda expressions, which cannot be saved"
2089 print >>sys.stderr, " back. The modifed script will be written into a separate file to"
2090 print >>sys.stderr, " minimize data loss."
2091 ScriptTainted = True
2092 return "here_was_a_lambda_expression_that_could_not_be_saved"
2093 elif type(value) == types.ClassType:
2094 return value.__name__
2095 elif type(value) == types.InstanceType:
2096 return value.__class__.__name__
2097 elif type(value) == types.DictType:
2098 return "{ " + ", ".join([PropValueRepr(k) + ": " + PropValueRepr(value[k]) for k in value]) + " }"
2099 else:
2100 return repr(value)
2102 # generate a nicely formatted string representation of a page's properties
2103 def SinglePagePropRepr(page):
2104 props = GetPublicProps(PageProps[page])
2105 if not props: return None
2106 return "\n%3d: {%s\n }" % (page, \
2107 ",".join(["\n " + repr(prop) + ": " + PropValueRepr(props[prop]) for prop in props]))
2109 # generate a nicely formatted string representation of all page properties
2110 def PagePropRepr():
2111 pages = PageProps.keys()
2112 pages.sort()
2113 return "PageProps = {%s\n}" % (",".join(filter(None, map(SinglePagePropRepr, pages))))
2115 # count the characters of a python dictionary source code, correctly handling
2116 # embedded strings and comments, and nested dictionaries
2117 def CountDictChars(s, start=0):
2118 context = None
2119 level = 0
2120 for i in xrange(start, len(s)):
2121 c = s[i]
2122 if context is None:
2123 if c == '{': level += 1
2124 if c == '}': level -= 1
2125 if c == '#': context = '#'
2126 if c == '"': context = '"'
2127 if c == "'": context = "'"
2128 elif context[0] == "\\":
2129 context=context[1]
2130 elif context == '#':
2131 if c in "\r\n": context = None
2132 elif context == '"':
2133 if c == "\\": context = "\\\""
2134 if c == '"': context = None
2135 elif context == "'":
2136 if c == "\\": context = "\\'"
2137 if c == "'": context = None
2138 if level < 0: return i
2139 raise ValueError, "the dictionary never ends"
2141 # modify and save a file's info script
2142 def SaveInfoScript(filename):
2143 # read the old info script
2144 try:
2145 f = file(filename, "r")
2146 script = f.read()
2147 f.close()
2148 except IOError:
2149 script = ""
2150 if not script:
2151 script = "# -*- coding: iso-8859-1 -*-\n"
2153 # replace the PageProps of the old info script with the current ones
2154 try:
2155 m = re.search("^.*(PageProps)\s*=\s*(\{).*$", script,re.MULTILINE)
2156 if m:
2157 script = script[:m.start(1)] + PagePropRepr() + \
2158 script[CountDictChars(script, m.end(2)) + 1 :]
2159 else:
2160 script += "\n" + PagePropRepr() + "\n"
2161 except (AttributeError, ValueError):
2162 pass
2164 if ScriptTainted:
2165 filename += ".modified"
2167 # write the script back
2168 try:
2169 f = file(filename, "w")
2170 f.write(script)
2171 f.close()
2172 except:
2173 print >>sys.stderr, "Oops! Could not write info script!"
2176 ##### OPENGL RENDERING #########################################################
2178 # draw OSD overlays
2179 def DrawOverlays():
2180 reltime = pygame.time.get_ticks() - StartTime
2181 if EstimatedDuration and (OverviewMode or GetPageProp(Pcurrent, 'progress', True)):
2182 rel = (0.001 * reltime) / EstimatedDuration
2183 x = int(ScreenWidth * rel)
2184 y = 1.0 - ProgressBarSize * PixelX
2185 a = min(255, max(0, x - ScreenWidth))
2186 b = min(255, max(0, x - ScreenWidth - 256))
2187 r = a
2188 g = 255 - b
2189 b = 0
2190 glDisable(TextureTarget)
2191 glDisable(GL_TEXTURE_2D)
2192 glEnable(GL_BLEND)
2193 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
2194 glBegin(GL_QUADS)
2195 glColor4ub(r, g, b, 0)
2196 glVertex2d(0, y)
2197 glVertex2d(rel, y)
2198 glColor4ub(r, g, b, ProgressBarAlpha)
2199 glVertex2d(rel, 1.0)
2200 glVertex2d(0, 1.0)
2201 glEnd()
2202 glDisable(GL_BLEND)
2203 if WantStatus:
2204 DrawOSDEx(OSDStatusPos, CurrentOSDStatus)
2205 if TimeDisplay:
2206 t = reltime / 1000
2207 DrawOSDEx(OSDTimePos, FormatTime(t, MinutesOnly))
2208 if CurrentOSDComment and (OverviewMode or not(TransitionRunning)):
2209 DrawOSD(ScreenWidth/2, \
2210 ScreenHeight - 3*OSDMargin - FontSize, \
2211 CurrentOSDComment, Center, Up)
2212 if CursorImage and CursorVisible:
2213 x, y = pygame.mouse.get_pos()
2214 x -= CursorHotspot[0]
2215 y -= CursorHotspot[1]
2216 X0 = x * PixelX
2217 Y0 = y * PixelY
2218 X1 = X0 + CursorSX
2219 Y1 = Y0 + CursorSY
2220 glDisable(TextureTarget)
2221 glEnable(GL_TEXTURE_2D)
2222 glBindTexture(GL_TEXTURE_2D, CursorTexture)
2223 glEnable(GL_BLEND)
2224 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
2225 glColor4ub(255, 255, 255, 255)
2226 glBegin(GL_QUADS)
2227 glTexCoord2d(0.0, 0.0); glVertex2d(X0, Y0)
2228 glTexCoord2d(CursorTX, 0.0); glVertex2d(X1, Y0)
2229 glTexCoord2d(CursorTX, CursorTY); glVertex2d(X1, Y1)
2230 glTexCoord2d(0.0, CursorTY); glVertex2d(X0, Y1)
2231 glEnd()
2232 glDisable(GL_BLEND)
2233 glDisable(GL_TEXTURE_2D)
2235 # draw the complete image of the current page
2236 def DrawCurrentPage(dark=1.0, do_flip=True):
2237 if VideoPlaying: return
2238 boxes = GetPageProp(Pcurrent, 'boxes')
2239 glClear(GL_COLOR_BUFFER_BIT)
2241 # pre-transform for zoom
2242 glLoadIdentity()
2243 glOrtho(ZoomX0, ZoomX0 + ZoomArea, ZoomY0 + ZoomArea, ZoomY0, -10.0, 10.0)
2245 # background layer -- the page's image, darkened if it has boxes
2246 glDisable(GL_BLEND)
2247 glEnable(TextureTarget)
2248 glBindTexture(TextureTarget, Tcurrent)
2249 if boxes or Tracing:
2250 light = 1.0 - 0.25 * dark
2251 else:
2252 light = 1.0
2253 glColor3d(light, light, light)
2254 DrawFullQuad()
2256 if boxes or Tracing:
2257 # alpha-blend the same image some times to blur it
2258 EnableAlphaBlend()
2259 DrawTranslatedFullQuad(+PixelX * ZoomArea, 0.0, light, dark / 2)
2260 DrawTranslatedFullQuad(-PixelX * ZoomArea, 0.0, light, dark / 3)
2261 DrawTranslatedFullQuad(0.0, +PixelY * ZoomArea, light, dark / 4)
2262 DrawTranslatedFullQuad(0.0, -PixelY * ZoomArea, light, dark / 5)
2264 if boxes:
2265 # draw outer box fade
2266 EnableAlphaBlend()
2267 for X0, Y0, X1, Y1 in boxes:
2268 glBegin(GL_QUAD_STRIP)
2269 DrawPointEx(X0, Y0, 1); DrawPointEx(X0 - EdgeX, Y0 - EdgeY, 0)
2270 DrawPointEx(X1, Y0, 1); DrawPointEx(X1 + EdgeX, Y0 - EdgeY, 0)
2271 DrawPointEx(X1, Y1, 1); DrawPointEx(X1 + EdgeX, Y1 + EdgeY, 0)
2272 DrawPointEx(X0, Y1, 1); DrawPointEx(X0 - EdgeX, Y1 + EdgeY, 0)
2273 DrawPointEx(X0, Y0, 1); DrawPointEx(X0 - EdgeX, Y0 - EdgeY, 0)
2274 glEnd()
2276 # draw boxes
2277 glDisable(GL_BLEND)
2278 glBegin(GL_QUADS)
2279 for X0, Y0, X1, Y1 in boxes:
2280 DrawPoint(X0, Y0)
2281 DrawPoint(X1, Y0)
2282 DrawPoint(X1, Y1)
2283 DrawPoint(X0, Y1)
2284 glEnd()
2286 if Tracing:
2287 x, y = MouseToScreen(pygame.mouse.get_pos())
2288 # outer spot fade
2289 EnableAlphaBlend()
2290 glBegin(GL_TRIANGLE_STRIP)
2291 for x0, y0, x1, y1 in SpotMesh:
2292 DrawPointEx(x + x0, y + y0, 1)
2293 DrawPointEx(x + x1, y + y1, 0)
2294 glEnd()
2295 # inner spot
2296 glDisable(GL_BLEND)
2297 glBegin(GL_TRIANGLE_FAN)
2298 DrawPoint(x, y)
2299 for x0, y0, x1, y1 in SpotMesh:
2300 DrawPoint(x + x0, y + y0)
2301 glEnd()
2303 if Marking:
2304 # soft alpha-blended rectangle
2305 glDisable(TextureTarget)
2306 glColor4d(*MarkColor)
2307 EnableAlphaBlend()
2308 glBegin(GL_QUADS)
2309 glVertex2d(MarkUL[0], MarkUL[1])
2310 glVertex2d(MarkLR[0], MarkUL[1])
2311 glVertex2d(MarkLR[0], MarkLR[1])
2312 glVertex2d(MarkUL[0], MarkLR[1])
2313 glEnd()
2314 # bright red frame
2315 glDisable(GL_BLEND)
2316 glBegin(GL_LINE_STRIP)
2317 glVertex2d(MarkUL[0], MarkUL[1])
2318 glVertex2d(MarkLR[0], MarkUL[1])
2319 glVertex2d(MarkLR[0], MarkLR[1])
2320 glVertex2d(MarkUL[0], MarkLR[1])
2321 glVertex2d(MarkUL[0], MarkUL[1])
2322 glEnd()
2323 glEnable(TextureTarget)
2325 # unapply the zoom transform
2326 glLoadIdentity()
2327 glOrtho(0.0, 1.0, 1.0, 0.0, -10.0, 10.0)
2329 # Done.
2330 DrawOverlays()
2331 if do_flip:
2332 pygame.display.flip()
2334 # draw a black screen with the Accentuate logo at the center
2335 def DrawLogo():
2336 glClear(GL_COLOR_BUFFER_BIT)
2337 glColor3ub(255, 255, 255)
2338 if TextureTarget != GL_TEXTURE_2D:
2339 glDisable(TextureTarget)
2340 glEnable(GL_TEXTURE_2D)
2341 glBindTexture(GL_TEXTURE_2D, LogoTexture)
2342 glBegin(GL_QUADS)
2343 glTexCoord2d(0, 0); glVertex2d(0.5 - 128.0 / ScreenWidth, 0.5 - 32.0 / ScreenHeight)
2344 glTexCoord2d(1, 0); glVertex2d(0.5 + 128.0 / ScreenWidth, 0.5 - 32.0 / ScreenHeight)
2345 glTexCoord2d(1, 1); glVertex2d(0.5 + 128.0 / ScreenWidth, 0.5 + 32.0 / ScreenHeight)
2346 glTexCoord2d(0, 1); glVertex2d(0.5 - 128.0 / ScreenWidth, 0.5 + 32.0 / ScreenHeight)
2347 glEnd()
2348 if OSDFont:
2349 OSDFont.Draw((ScreenWidth / 2, ScreenHeight / 2 + 48), \
2350 __version__, align=Center, alpha=0.25)
2351 glDisable(GL_TEXTURE_2D)
2353 # draw the prerender progress bar
2354 def DrawProgress(position):
2355 glDisable(TextureTarget)
2356 x0 = 0.1
2357 x2 = 1.0 - x0
2358 x1 = position * x2 + (1.0 - position) * x0
2359 y1 = 0.9
2360 y0 = y1 - 16.0 / ScreenHeight
2361 glBegin(GL_QUADS)
2362 glColor3ub( 64, 64, 64); glVertex2d(x0, y0); glVertex2d(x2, y0)
2363 glColor3ub(128, 128, 128); glVertex2d(x2, y1); glVertex2d(x0, y1)
2364 glColor3ub( 64, 128, 255); glVertex2d(x0, y0); glVertex2d(x1, y0)
2365 glColor3ub( 8, 32, 128); glVertex2d(x1, y1); glVertex2d(x0, y1)
2366 glEnd()
2367 glEnable(TextureTarget)
2369 # fade mode
2370 def DrawFadeMode(intensity, alpha):
2371 if VideoPlaying: return
2372 DrawCurrentPage(do_flip=False)
2373 glDisable(TextureTarget)
2374 EnableAlphaBlend()
2375 glColor4d(intensity, intensity, intensity, alpha)
2376 DrawFullQuad()
2377 glEnable(TextureTarget)
2378 pygame.display.flip()
2380 def FadeMode(intensity):
2381 t0 = pygame.time.get_ticks()
2382 while True:
2383 if pygame.event.get([KEYDOWN,MOUSEBUTTONUP]): break
2384 t = (pygame.time.get_ticks() - t0) * 1.0 / BlankFadeDuration
2385 if t >= 1.0: break
2386 DrawFadeMode(intensity, t)
2387 DrawFadeMode(intensity, 1.0)
2389 while True:
2390 event = pygame.event.wait()
2391 if event.type == QUIT:
2392 PageLeft()
2393 Quit()
2394 elif event.type == VIDEOEXPOSE:
2395 DrawFadeMode(intensity, 1.0)
2396 elif event.type == MOUSEBUTTONUP:
2397 break
2398 elif event.type == KEYDOWN:
2399 if event.unicode == u'q':
2400 pygame.event.post(pygame.event.Event(QUIT))
2401 else:
2402 break
2404 t0 = pygame.time.get_ticks()
2405 while True:
2406 if pygame.event.get([KEYDOWN,MOUSEBUTTONUP]): break
2407 t = (pygame.time.get_ticks() - t0) * 1.0 / BlankFadeDuration
2408 if t >= 1.0: break
2409 DrawFadeMode(intensity, 1.0 - t)
2410 DrawCurrentPage()
2412 # gamma control
2413 def SetGamma(new_gamma=None, new_black=None, force=False):
2414 global Gamma, BlackLevel
2415 if new_gamma is None: new_gamma = Gamma
2416 if new_gamma < 0.1: new_gamma = 0.1
2417 if new_gamma > 10.0: new_gamma = 10.0
2418 if new_black is None: new_black = BlackLevel
2419 if new_black < 0: new_black = 0
2420 if new_black > 254: new_black = 254
2421 if not(force) and (abs(Gamma - new_gamma) < 0.01) and (new_black == BlackLevel):
2422 return
2423 Gamma = new_gamma
2424 BlackLevel = new_black
2425 scale = 1.0 / (255 - BlackLevel)
2426 power = 1.0 / Gamma
2427 ramp = [int(65535.0 * ((max(0, x - BlackLevel) * scale) ** power)) for x in range(256)]
2428 return pygame.display.set_gamma_ramp(ramp, ramp, ramp)
2430 # cursor image
2431 def PrepareCustomCursor(cimg):
2432 global CursorTexture, CursorSX, CursorSY, CursorTX, CursorTY
2433 w, h = cimg.size
2434 tw, th = map(npot, cimg.size)
2435 if (tw > 256) or (th > 256):
2436 print >>sys.stderr, "Custom cursor is rediculously large, reverting to normal one."
2437 return False
2438 img = Image.new('RGBA', (tw, th))
2439 img.paste(cimg, (0, 0))
2440 CursorTexture = glGenTextures(1)
2441 glBindTexture(GL_TEXTURE_2D, CursorTexture)
2442 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tw, th, 0, GL_RGBA, GL_UNSIGNED_BYTE, img.tostring())
2443 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
2444 CursorSX = w * PixelX
2445 CursorSY = h * PixelY
2446 CursorTX = w / float(tw)
2447 CursorTY = h / float(th)
2448 return True
2451 ##### CONTROL AND NAVIGATION ###################################################
2453 # update the applications' title bar
2454 def UpdateCaption(page=0, force=False):
2455 global CurrentCaption, CurrentOSDCaption, CurrentOSDPage, CurrentOSDStatus
2456 global CurrentOSDComment
2457 if (page == CurrentCaption) and not(force):
2458 return
2459 CurrentCaption = page
2460 caption = __title__
2461 if DocumentTitle:
2462 caption += " - " + DocumentTitle
2463 if page < 1:
2464 CurrentOSDCaption = ""
2465 CurrentOSDPage = ""
2466 CurrentOSDStatus = ""
2467 CurrentOSDComment = ""
2468 pygame.display.set_caption(caption, __title__)
2469 return
2470 CurrentOSDPage = "%d/%d" % (page, PageCount)
2471 caption = "%s (%s)" % (caption, CurrentOSDPage)
2472 title = GetPageProp(page, 'title') or GetPageProp(page, '_title')
2473 if title:
2474 caption += ": %s" % title
2475 CurrentOSDCaption = title
2476 else:
2477 CurrentOSDCaption = ""
2478 status = []
2479 if GetPageProp(page, 'skip', False):
2480 status.append("skipped: yes")
2481 if not GetPageProp(page, ('overview', '_overview'), True):
2482 status.append("on overview page: no")
2483 CurrentOSDStatus = ", ".join(status)
2484 CurrentOSDComment = GetPageProp(page, 'comment')
2485 pygame.display.set_caption(caption, __title__)
2487 # get next/previous page
2488 def GetNextPage(page, direction, keypressed=0):
2489 try_page = page
2490 while True:
2491 try_page += direction
2492 if try_page == page:
2493 return 0 # tried all pages, but none found
2494 if Wrap:
2495 if try_page < 1: try_page = PageCount
2496 if try_page > PageCount: try_page = 1
2497 elif (FadeToBlackAtEnd and keypressed):
2498 if try_page > PageCount:
2499 FadeMode(0.0)
2500 else:
2501 if try_page < 1 or try_page > PageCount:
2502 return 0 # start or end of presentation
2503 if not GetPageProp(try_page, 'skip', False):
2504 return try_page
2506 # pre-load the following page into Pnext/Tnext
2507 def PreloadNextPage(page):
2508 global Pnext, Tnext
2509 if (page < 1) or (page > PageCount):
2510 Pnext = 0
2511 return 0
2512 if page == Pnext:
2513 return 1
2514 RenderPage(page, Tnext)
2515 Pnext = page
2516 return 1
2518 # perform box fading; the fade animation time is mapped through func()
2519 def BoxFade(func):
2520 t0 = pygame.time.get_ticks()
2521 while 1:
2522 if pygame.event.get([KEYDOWN,MOUSEBUTTONUP]): break
2523 t = (pygame.time.get_ticks() - t0) * 1.0 / BoxFadeDuration
2524 if t >= 1.0: break
2525 DrawCurrentPage(func(t))
2526 DrawCurrentPage(func(1.0))
2527 return 0
2529 # reset the timer
2530 def ResetTimer():
2531 global StartTime, PageEnterTime
2532 if TimeTracking and not(FirstPage):
2533 print "--- timer was reset here ---"
2534 StartTime = pygame.time.get_ticks()
2535 PageEnterTime = 0
2537 # start video playback
2538 def PlayVideo(video):
2539 global MPlayerPID, VideoPlaying
2540 if not video: return
2541 StopMPlayer()
2542 try:
2543 MPlayerPID = spawn(os.P_NOWAIT, \
2544 MPlayerPath, [MPlayerPath, "-quiet", \
2545 "-monitorpixelaspect", "1:1", "-autosync", "100"] + \
2546 MPlayerPlatformOptions + [ "-slave", \
2547 "-wid", str(pygame.display.get_wm_info()['window']), \
2548 FileNameEscape + video + FileNameEscape])
2549 if MPlayerColorKey:
2550 glClear(GL_COLOR_BUFFER_BIT)
2551 pygame.display.flip()
2552 VideoPlaying = True
2553 except OSError:
2554 MPlayerPID = 0
2556 # called each time a page is entered
2557 def PageEntered(update_time=True):
2558 global PageEnterTime, MPlayerPID, IsZoomed, WantStatus
2559 if update_time:
2560 PageEnterTime = pygame.time.get_ticks() - StartTime
2561 IsZoomed = False # no, we don't have a pre-zoomed image right now
2562 WantStatus = False # don't show status unless it's changed interactively
2563 timeout = AutoAdvance
2564 shown = GetPageProp(Pcurrent, '_shown', 0)
2565 if not shown:
2566 timeout = GetPageProp(Pcurrent, 'timeout', timeout)
2567 video = GetPageProp(Pcurrent, 'video')
2568 sound = GetPageProp(Pcurrent, 'sound')
2569 PlayVideo(video)
2570 if sound and not(video):
2571 StopMPlayer()
2572 try:
2573 MPlayerPID = spawn(os.P_NOWAIT, \
2574 MPlayerPath, [MPlayerPath, "-quiet", "-really-quiet", \
2575 FileNameEscape + sound + FileNameEscape])
2576 except OSError:
2577 MPlayerPID = 0
2578 SafeCall(GetPageProp(Pcurrent, 'OnEnterOnce'))
2579 SafeCall(GetPageProp(Pcurrent, 'OnEnter'))
2580 if timeout: pygame.time.set_timer(USEREVENT_PAGE_TIMEOUT, timeout)
2581 SetPageProp(Pcurrent, '_shown', shown + 1)
2583 # called each time a page is left
2584 def PageLeft(overview=False):
2585 global FirstPage, LastPage, WantStatus
2586 WantStatus = False
2587 if not overview:
2588 if GetTristatePageProp(Pcurrent, 'reset'):
2589 ResetTimer()
2590 FirstPage = False
2591 LastPage = Pcurrent
2592 if GetPageProp(Pcurrent, '_shown', 0) == 1:
2593 SafeCall(GetPageProp(Pcurrent, 'OnLeaveOnce'))
2594 SafeCall(GetPageProp(Pcurrent, 'OnLeave'))
2595 if TimeTracking:
2596 t1 = pygame.time.get_ticks() - StartTime
2597 dt = (t1 - PageEnterTime + 500) / 1000
2598 if overview:
2599 p = "over"
2600 else:
2601 p = "%4d" % Pcurrent
2602 print "%s%9s%9s%9s" % (p, FormatTime(dt), \
2603 FormatTime(PageEnterTime / 1000), \
2604 FormatTime(t1 / 1000))
2606 # perform a transition to a specified page
2607 def TransitionTo(page):
2608 global Pcurrent, Pnext, Tcurrent, Tnext
2609 global PageCount, Marking, Tracing, Panning, TransitionRunning
2611 # first, stop the auto-timer
2612 pygame.time.set_timer(USEREVENT_PAGE_TIMEOUT, 0)
2614 # invalid page? go away
2615 if not PreloadNextPage(page):
2616 return 0
2618 # notify that the page has been left
2619 PageLeft()
2621 # box fade-out
2622 if GetPageProp(Pcurrent, 'boxes') or Tracing:
2623 skip = BoxFade(lambda t: 1.0 - t)
2624 else:
2625 skip = 0
2627 # some housekeeping
2628 Marking = False
2629 Tracing = False
2630 UpdateCaption(page)
2632 # check if the transition is valid
2633 tpage = min(Pcurrent, Pnext)
2634 if 'transition' in PageProps[tpage]:
2635 tkey = 'transition'
2636 else:
2637 tkey = '_transition'
2638 trans = PageProps[tpage][tkey]
2639 if trans is None:
2640 transtime = 0
2641 else:
2642 transtime = GetPageProp(tpage, 'transtime', TransitionDuration)
2643 try:
2644 dummy = trans.__class__
2645 except AttributeError:
2646 # ah, gotcha! the transition is not yet intantiated!
2647 trans = trans()
2648 PageProps[tpage][tkey] = trans
2650 # backward motion? then swap page buffers now
2651 backward = (Pnext < Pcurrent)
2652 if backward:
2653 Pcurrent, Pnext = (Pnext, Pcurrent)
2654 Tcurrent, Tnext = (Tnext, Tcurrent)
2656 # transition animation
2657 if not(skip) and transtime:
2658 transtime = 1.0 / transtime
2659 TransitionRunning = True
2660 t0 = pygame.time.get_ticks()
2661 while not(VideoPlaying):
2662 if pygame.event.get([KEYDOWN,MOUSEBUTTONUP]):
2663 skip = 1
2664 break
2665 t = (pygame.time.get_ticks() - t0) * transtime
2666 if t >= 1.0: break
2667 if backward: t = 1.0 - t
2668 glEnable(TextureTarget)
2669 trans.render(t)
2670 DrawOverlays()
2671 pygame.display.flip()
2672 TransitionRunning = False
2674 # forward motion => swap page buffers now
2675 if not backward:
2676 Pcurrent, Pnext = (Pnext, Pcurrent)
2677 Tcurrent, Tnext = (Tnext, Tcurrent)
2679 # box fade-in
2680 if not(skip) and GetPageProp(Pcurrent, 'boxes'): BoxFade(lambda t: t)
2682 # finally update the screen and preload the next page
2683 DrawCurrentPage() # I do that twice because for some strange reason, the
2684 PageEntered()
2685 if not PreloadNextPage(GetNextPage(Pcurrent, 1)):
2686 PreloadNextPage(GetNextPage(Pcurrent, -1))
2687 return 1
2689 # zoom mode animation
2690 def ZoomAnimation(targetx, targety, func):
2691 global ZoomX0, ZoomY0, ZoomArea
2692 t0 = pygame.time.get_ticks()
2693 while True:
2694 if pygame.event.get([KEYDOWN,MOUSEBUTTONUP]): break
2695 t = (pygame.time.get_ticks() - t0)* 1.0 / ZoomDuration
2696 if t >= 1.0: break
2697 t = func(t)
2698 t = (2.0 - t) * t
2699 ZoomX0 = targetx * t
2700 ZoomY0 = targety * t
2701 ZoomArea = 1.0 - 0.5 * t
2702 DrawCurrentPage()
2703 t = func(1.0)
2704 ZoomX0 = targetx * t
2705 ZoomY0 = targety * t
2706 ZoomArea = 1.0 - 0.5 * t
2707 GenerateSpotMesh()
2708 DrawCurrentPage()
2710 # enter zoom mode
2711 def EnterZoomMode(targetx, targety):
2712 global ZoomMode, IsZoomed, ZoomWarningIssued
2713 ZoomAnimation(targetx, targety, lambda t: t)
2714 ZoomMode = True
2715 if TextureTarget != GL_TEXTURE_2D:
2716 if not ZoomWarningIssued:
2717 print >>sys.stderr, "Sorry, but I can't increase the detail level in zoom mode any further when"
2718 print >>sys.stderr, "GL_ARB_texture_rectangle is used. Please try running Accentuate with the"
2719 print >>sys.stderr, "'-e' parameter. If a modern nVidia or ATI graphics card is used, a driver"
2720 print >>sys.stderr, "update may also fix the problem."
2721 ZoomWarningIssued = True
2722 return
2723 if not IsZoomed:
2724 glBindTexture(TextureTarget, Tcurrent)
2725 try:
2726 glTexImage2D(TextureTarget, 0, 3, TexWidth * 2, TexHeight * 2, 0, \
2727 GL_RGB, GL_UNSIGNED_BYTE, PageImage(Pcurrent, True))
2728 except GLerror:
2729 if not ZoomWarningIssued:
2730 print >>sys.stderr, "Sorry, but I can't increase the detail level in zoom mode any further, because"
2731 print >>sys.stderr, "your OpenGL implementation does not support that. Either the texture memory is"
2732 print >>sys.stderr, "exhausted, or there is no support for large textures (%dx%d). If you really" % (TexWidth * 2, TexHeight * 2)
2733 print >>sys.stderr, "need high-res zooming, please try to run Accentuate in a smaller resolution"
2734 print >>sys.stderr, "using the -g command-line option."
2735 ZoomWarningIssued = True
2736 return
2737 DrawCurrentPage()
2738 IsZoomed = True
2740 # leave zoom mode (if enabled)
2741 def LeaveZoomMode():
2742 global ZoomMode
2743 if not ZoomMode: return
2744 ZoomAnimation(ZoomX0, ZoomY0, lambda t: 1.0 - t)
2745 ZoomMode = False
2746 Panning = False
2748 # increment/decrement spot radius
2749 def IncrementSpotSize(delta):
2750 global SpotRadius
2751 if not Tracing:
2752 return
2753 SpotRadius = max(SpotRadius + delta, 8)
2754 GenerateSpotMesh()
2755 DrawCurrentPage()
2757 # post-initialize the page transitions
2758 def PrepareTransitions():
2759 Unspecified = 0xAFFED00F
2760 # STEP 1: randomly assign transitions where the user didn't specify them
2761 cnt = sum([1 for page in xrange(1, PageCount + 1) \
2762 if GetPageProp(page, 'transition', Unspecified) == Unspecified])
2763 newtrans = ((cnt / len(AvailableTransitions) + 1) * AvailableTransitions)[:cnt]
2764 random.shuffle(newtrans)
2765 for page in xrange(1, PageCount + 1):
2766 if GetPageProp(page, 'transition', Unspecified) == Unspecified:
2767 SetPageProp(page, '_transition', newtrans.pop())
2768 # STEP 2: instantiate transitions
2769 for page in PageProps:
2770 for key in ('transition', '_transition'):
2771 if not key in PageProps[page]:
2772 continue
2773 trans = PageProps[page][key]
2774 if trans is not None:
2775 PageProps[page][key] = trans()
2777 # update timer values and screen timer
2778 def TimerTick():
2779 global CurrentTime, ProgressBarPos
2780 redraw = False
2781 newtime = (pygame.time.get_ticks() - StartTime) * 0.001
2782 if EstimatedDuration:
2783 newpos = int(ScreenWidth * newtime / EstimatedDuration)
2784 if newpos != ProgressBarPos:
2785 redraw = True
2786 ProgressBarPos = newpos
2787 newtime = int(newtime)
2788 if TimeDisplay and (CurrentTime != newtime):
2789 redraw = True
2790 CurrentTime = newtime
2791 return redraw
2793 # set cursor visibility
2794 def SetCursor(visible):
2795 global CursorVisible
2796 CursorVisible = visible
2797 if not CursorImage:
2798 pygame.mouse.set_visible(visible)
2800 # shortcut handling
2801 def IsValidShortcutKey(key):
2802 return ((key >= K_a) and (key <= K_z)) \
2803 or ((key >= K_0) and (key <= K_9)) \
2804 or ((key >= K_F1) and (key <= K_F12))
2805 def FindShortcut(shortcut):
2806 for page, props in PageProps.iteritems():
2807 try:
2808 check = props['shortcut']
2809 if type(check) != types.StringType:
2810 check = int(check)
2811 elif (len(check) > 1) and (check[0] in "Ff"):
2812 check = K_F1 - 1 + int(check[1:])
2813 else:
2814 check = ord(check.lower())
2815 except (KeyError, TypeError, ValueError):
2816 continue
2817 if check == shortcut:
2818 return page
2819 return None
2820 def AssignShortcut(page, key):
2821 old_page = FindShortcut(key)
2822 if old_page:
2823 del PageProps[old_page]['shortcut']
2824 if key < 127:
2825 shortcut = chr(key)
2826 elif (key >= K_F1) and (key <= K_F15):
2827 shortcut = "F%d" % (key - K_F1 + 1)
2828 else:
2829 shortcut = int(key)
2830 SetPageProp(page, 'shortcut', shortcut)
2833 ##### OVERVIEW MODE ############################################################
2835 def UpdateOverviewTexture():
2836 global OverviewNeedUpdate
2837 glBindTexture(TextureTarget, Tnext)
2838 Loverview.acquire()
2839 try:
2840 glTexImage2D(TextureTarget, 0, 3, TexWidth, TexHeight, 0, \
2841 GL_RGB, GL_UNSIGNED_BYTE, OverviewImage.tostring())
2842 finally:
2843 Loverview.release()
2844 OverviewNeedUpdate = False
2846 # draw the overview page
2847 def DrawOverview():
2848 if VideoPlaying: return
2849 glClear(GL_COLOR_BUFFER_BIT)
2850 glDisable(GL_BLEND)
2851 glEnable(TextureTarget)
2852 glBindTexture(TextureTarget, Tnext)
2853 glColor3ub(192, 192, 192)
2854 DrawFullQuad()
2856 pos = OverviewPos(OverviewSelection)
2857 X0 = PixelX * pos[0]
2858 Y0 = PixelY * pos[1]
2859 X1 = PixelX * (pos[0] + OverviewCellX)
2860 Y1 = PixelY * (pos[1] + OverviewCellY)
2861 glColor3d(1.0, 1.0, 1.0)
2862 glBegin(GL_QUADS)
2863 DrawPoint(X0, Y0)
2864 DrawPoint(X1, Y0)
2865 DrawPoint(X1, Y1)
2866 DrawPoint(X0, Y1)
2867 glEnd()
2869 DrawOSDEx(OSDTitlePos, CurrentOSDCaption)
2870 DrawOSDEx(OSDPagePos, CurrentOSDPage)
2871 DrawOSDEx(OSDStatusPos, CurrentOSDStatus)
2872 DrawOverlays()
2873 pygame.display.flip()
2875 # overview zoom effect, time mapped through func
2876 def OverviewZoom(func):
2877 global TransitionRunning
2878 pos = OverviewPos(OverviewSelection)
2879 X0 = PixelX * (pos[0] + OverviewBorder)
2880 Y0 = PixelY * (pos[1] + OverviewBorder)
2881 X1 = PixelX * (pos[0] - OverviewBorder + OverviewCellX)
2882 Y1 = PixelY * (pos[1] - OverviewBorder + OverviewCellY)
2884 TransitionRunning = True
2885 t0 = pygame.time.get_ticks()
2886 while not(VideoPlaying):
2887 t = (pygame.time.get_ticks() - t0) * 1.0 / ZoomDuration
2888 if t >= 1.0: break
2889 t = func(t)
2890 t1 = t*t
2891 t = 1.0 - t1
2893 zoom = (t * (X1 - X0) + t1) / (X1 - X0)
2894 OX = zoom * (t * X0 - X0) - (zoom - 1.0) * t * X0
2895 OY = zoom * (t * Y0 - Y0) - (zoom - 1.0) * t * Y0
2896 OX = t * X0 - zoom * X0
2897 OY = t * Y0 - zoom * Y0
2899 glDisable(GL_BLEND)
2900 glEnable(TextureTarget)
2901 glBindTexture(TextureTarget, Tnext)
2902 glBegin(GL_QUADS)
2903 glColor3ub(192, 192, 192)
2904 glTexCoord2d( 0.0, 0.0); glVertex2d(OX, OY)
2905 glTexCoord2d(TexMaxS, 0.0); glVertex2d(OX + zoom, OY)
2906 glTexCoord2d(TexMaxS, TexMaxT); glVertex2d(OX + zoom, OY + zoom)
2907 glTexCoord2d( 0.0, TexMaxT); glVertex2d(OX, OY + zoom)
2908 glColor3ub(255, 255, 255)
2909 glTexCoord2d(X0 * TexMaxS, Y0 * TexMaxT); glVertex2d(OX + X0*zoom, OY + Y0 * zoom)
2910 glTexCoord2d(X1 * TexMaxS, Y0 * TexMaxT); glVertex2d(OX + X1*zoom, OY + Y0 * zoom)
2911 glTexCoord2d(X1 * TexMaxS, Y1 * TexMaxT); glVertex2d(OX + X1*zoom, OY + Y1 * zoom)
2912 glTexCoord2d(X0 * TexMaxS, Y1 * TexMaxT); glVertex2d(OX + X0*zoom, OY + Y1 * zoom)
2913 glEnd()
2915 EnableAlphaBlend()
2916 glBindTexture(TextureTarget, Tcurrent)
2917 glColor4d(1.0, 1.0, 1.0, 1.0 - t * t * t)
2918 glBegin(GL_QUADS)
2919 glTexCoord2d( 0.0, 0.0); glVertex2d(t * X0, t * Y0)
2920 glTexCoord2d(TexMaxS, 0.0); glVertex2d(t * X1 + t1, t * Y0)
2921 glTexCoord2d(TexMaxS, TexMaxT); glVertex2d(t * X1 + t1, t * Y1 + t1)
2922 glTexCoord2d( 0.0, TexMaxT); glVertex2d(t * X0, t * Y1 + t1)
2923 glEnd()
2925 DrawOSDEx(OSDTitlePos, CurrentOSDCaption, alpha_factor=t)
2926 DrawOSDEx(OSDPagePos, CurrentOSDPage, alpha_factor=t)
2927 DrawOSDEx(OSDStatusPos, CurrentOSDStatus, alpha_factor=t)
2928 DrawOverlays()
2929 pygame.display.flip()
2930 TransitionRunning = False
2932 # overview keyboard navigation
2933 def OverviewKeyboardNav(delta):
2934 global OverviewSelection
2935 dest = OverviewSelection + delta
2936 if (dest >= OverviewPageCount) or (dest < 0):
2937 return
2938 OverviewSelection = dest
2939 x, y = OverviewPos(OverviewSelection)
2940 pygame.mouse.set_pos((x + (OverviewCellX / 2), y + (OverviewCellY / 2)))
2942 # overview mode PageProp toggle
2943 def OverviewTogglePageProp(prop, default):
2944 if (OverviewSelection < 0) or (OverviewSelection >= len(OverviewPageMap)):
2945 return
2946 page = OverviewPageMap[OverviewSelection]
2947 SetPageProp(page, prop, not(GetPageProp(page, prop, default)))
2948 UpdateCaption(page, force=True)
2949 DrawOverview()
2951 # overview event handler
2952 def HandleOverviewEvent(event):
2953 global OverviewSelection, TimeDisplay
2955 if event.type == QUIT:
2956 PageLeft(overview=True)
2957 Quit()
2958 elif event.type == VIDEOEXPOSE:
2959 DrawOverview()
2961 elif event.type == KEYDOWN:
2962 if (event.key == K_ESCAPE) or (event.unicode == u'q'):
2963 pygame.event.post(pygame.event.Event(QUIT))
2964 elif event.unicode == u'f':
2965 SetFullscreen(not Fullscreen)
2966 elif event.unicode == u't':
2967 TimeDisplay = not(TimeDisplay)
2968 DrawOverview()
2969 elif event.unicode == u'r':
2970 ResetTimer()
2971 if TimeDisplay: DrawOverview()
2972 elif event.unicode == u's':
2973 SaveInfoScript(InfoScriptPath)
2974 elif event.unicode == u'o':
2975 OverviewTogglePageProp('overview', GetPageProp(Pcurrent, '_overview', True))
2976 elif event.unicode == u'i':
2977 OverviewTogglePageProp('skip', False)
2978 elif event.key == K_UP: OverviewKeyboardNav(-OverviewGridSize)
2979 elif event.key == K_LEFT: OverviewKeyboardNav(-1)
2980 elif event.key == K_RIGHT: OverviewKeyboardNav(+1)
2981 elif event.key == K_DOWN: OverviewKeyboardNav(+OverviewGridSize)
2982 elif event.key == K_TAB:
2983 OverviewSelection = -1
2984 return 0
2985 elif event.key in (K_RETURN, K_KP_ENTER):
2986 return 0
2987 elif IsValidShortcutKey(event.key):
2988 if event.mod & KMOD_SHIFT:
2989 try:
2990 AssignShortcut(OverviewPageMap[OverviewSelection], event.key)
2991 except IndexError:
2992 pass # no valid page selected
2993 else:
2994 # load shortcut
2995 page = FindShortcut(event.key)
2996 if page:
2997 OverviewSelection = OverviewPageMapInv[page]
2998 x, y = OverviewPos(OverviewSelection)
2999 pygame.mouse.set_pos((x + (OverviewCellX / 2), \
3000 y + (OverviewCellY / 2)))
3001 DrawOverview()
3003 elif event.type == MOUSEBUTTONUP:
3004 if event.button == 1:
3005 return 0
3006 elif event.button in (2, 3):
3007 OverviewSelection = -1
3008 return 0
3010 elif event.type == MOUSEMOTION:
3011 pygame.event.clear(MOUSEMOTION)
3012 # mouse move in fullscreen mode -> show mouse cursor and reset mouse timer
3013 if Fullscreen:
3014 pygame.time.set_timer(USEREVENT_HIDE_MOUSE, MouseHideDelay)
3015 SetCursor(True)
3016 # determine highlighted page
3017 OverviewSelection = \
3018 int((event.pos[0] - OverviewOfsX) / OverviewCellX) + \
3019 int((event.pos[1] - OverviewOfsY) / OverviewCellY) * OverviewGridSize
3020 if (OverviewSelection < 0) or (OverviewSelection >= len(OverviewPageMap)):
3021 UpdateCaption(0)
3022 else:
3023 UpdateCaption(OverviewPageMap[OverviewSelection])
3024 DrawOverview()
3026 elif event.type == USEREVENT_HIDE_MOUSE:
3027 # mouse timer event -> hide fullscreen cursor
3028 pygame.time.set_timer(USEREVENT_HIDE_MOUSE, 0)
3029 SetCursor(False)
3030 DrawOverview()
3032 return 1
3034 # overview mode entry/loop/exit function
3035 def DoOverview():
3036 global Pcurrent, Pnext, Tcurrent, Tnext, Tracing, OverviewSelection
3037 global PageEnterTime, OverviewMode
3039 pygame.time.set_timer(USEREVENT_PAGE_TIMEOUT, 0)
3040 PageLeft()
3041 UpdateOverviewTexture()
3043 if GetPageProp(Pcurrent, 'boxes') or Tracing:
3044 BoxFade(lambda t: 1.0 - t)
3045 Tracing = False
3046 OverviewSelection = OverviewPageMapInv[Pcurrent]
3048 OverviewMode = True
3049 OverviewZoom(lambda t: 1.0 - t)
3050 DrawOverview()
3051 PageEnterTime = pygame.time.get_ticks() - StartTime
3052 while True:
3053 event = pygame.event.poll()
3054 if event.type == NOEVENT:
3055 force_update = OverviewNeedUpdate
3056 if OverviewNeedUpdate:
3057 UpdateOverviewTexture()
3058 if TimerTick() or force_update:
3059 DrawOverview()
3060 pygame.time.wait(20)
3061 elif not HandleOverviewEvent(event):
3062 break
3063 PageLeft(overview=True)
3065 if (OverviewSelection < 0) or (OverviewSelection >= OverviewPageCount):
3066 OverviewSelection = OverviewPageMapInv[Pcurrent]
3067 Pnext = Pcurrent
3068 else:
3069 Pnext = OverviewPageMap[OverviewSelection]
3070 if Pnext != Pcurrent:
3071 Pcurrent = Pnext
3072 RenderPage(Pcurrent, Tcurrent)
3073 UpdateCaption(Pcurrent)
3074 OverviewZoom(lambda t: t)
3075 OverviewMode = False
3076 DrawCurrentPage()
3078 if GetPageProp(Pcurrent, 'boxes'):
3079 BoxFade(lambda t: t)
3080 PageEntered()
3081 if not PreloadNextPage(GetNextPage(Pcurrent, 1)):
3082 PreloadNextPage(GetNextPage(Pcurrent, -1))
3085 ##### EVENT HANDLING ###########################################################
3087 # set fullscreen mode
3088 def SetFullscreen(fs, do_init=True):
3089 global Fullscreen
3091 # let pygame do the real work
3092 if do_init:
3093 if fs == Fullscreen: return
3094 if not pygame.display.toggle_fullscreen(): return
3095 Fullscreen=fs
3097 # redraw the current page (pygame is too lazy to send an expose event ...)
3098 DrawCurrentPage()
3100 # show cursor and set auto-hide timer
3101 if fs:
3102 pygame.time.set_timer(USEREVENT_HIDE_MOUSE, MouseHideDelay)
3103 else:
3104 pygame.time.set_timer(USEREVENT_HIDE_MOUSE, 0)
3105 SetCursor(True)
3107 # PageProp toggle
3108 def TogglePageProp(prop, default):
3109 global WantStatus
3110 SetPageProp(Pcurrent, prop, not(GetPageProp(Pcurrent, prop, default)))
3111 UpdateCaption(Pcurrent, force=True)
3112 WantStatus = True
3113 DrawCurrentPage()
3115 # main event handling function
3116 def HandleEvent(event):
3117 global HaveMark, ZoomMode, Marking, Tracing, Panning, SpotRadius, FileStats
3118 global MarkUL, MarkLR, MouseDownX, MouseDownY, PanAnchorX, PanAnchorY
3119 global ZoomX0, ZoomY0, RTrunning, RTrestart, StartTime, PageEnterTime
3120 global CurrentTime, TimeDisplay, TimeTracking, ProgressBarPos
3122 if event.type == QUIT:
3123 PageLeft()
3124 Quit()
3125 elif event.type == VIDEOEXPOSE:
3126 DrawCurrentPage()
3128 elif event.type == KEYDOWN:
3129 if VideoPlaying:
3130 StopMPlayer()
3131 DrawCurrentPage()
3132 elif (event.key == K_ESCAPE) or (event.unicode == u'q'):
3133 pygame.event.post(pygame.event.Event(QUIT))
3134 elif event.unicode == u'f':
3135 SetFullscreen(not Fullscreen)
3136 elif (event.key == K_TAB) and (event.mod & KMOD_ALT) and Fullscreen:
3137 SetFullscreen(False)
3138 elif event.unicode == u's':
3139 SaveInfoScript(InfoScriptPath)
3140 elif event.unicode == u'z': # handle QWERTY and QWERTZ keyboards
3141 if ZoomMode:
3142 LeaveZoomMode()
3143 else:
3144 tx, ty = MouseToScreen(pygame.mouse.get_pos())
3145 EnterZoomMode(0.5 * tx, 0.5 * ty)
3146 elif event.unicode == u'b':
3147 FadeMode(0.0)
3148 elif event.unicode == u'w':
3149 FadeMode(1.0)
3150 elif event.unicode == u't':
3151 TimeDisplay = not(TimeDisplay)
3152 DrawCurrentPage()
3153 if TimeDisplay and not(TimeTracking) and FirstPage:
3154 print >>sys.stderr, "Time tracking mode enabled."
3155 TimeTracking = True
3156 print "page duration enter leave"
3157 print "---- -------- -------- --------"
3158 elif event.unicode == u'r':
3159 ResetTimer()
3160 if TimeDisplay: DrawCurrentPage()
3161 elif event.unicode == u'l':
3162 TransitionTo(LastPage)
3163 elif event.unicode == u'o':
3164 TogglePageProp('overview', GetPageProp(Pcurrent, '_overview', True))
3165 elif event.unicode == u'i':
3166 TogglePageProp('skip', False)
3167 elif event.key == K_TAB:
3168 LeaveZoomMode()
3169 DoOverview()
3170 elif event.key in (32, K_DOWN, K_RIGHT, K_PAGEDOWN):
3171 LeaveZoomMode()
3172 TransitionTo(GetNextPage(Pcurrent, 1, 1))
3173 elif event.key in (K_BACKSPACE, K_UP, K_LEFT, K_PAGEUP):
3174 LeaveZoomMode()
3175 TransitionTo(GetNextPage(Pcurrent, -1, 1))
3176 elif event.key == K_HOME:
3177 if Pcurrent != 1:
3178 TransitionTo(1)
3179 elif event.key == K_END:
3180 if Pcurrent != PageCount:
3181 TransitionTo(PageCount)
3182 elif event.key in (K_RETURN, K_KP_ENTER):
3183 if not(GetPageProp(Pcurrent, 'boxes')) and Tracing:
3184 BoxFade(lambda t: 1.0 - t)
3185 Tracing = not(Tracing)
3186 if not(GetPageProp(Pcurrent, 'boxes')) and Tracing:
3187 BoxFade(lambda t: t)
3188 elif event.unicode == u'+':
3189 IncrementSpotSize(+8)
3190 elif event.unicode == u'-':
3191 IncrementSpotSize(-8)
3192 elif event.unicode == u'[':
3193 SetGamma(new_gamma=Gamma / GammaStep)
3194 elif event.unicode == u']':
3195 SetGamma(new_gamma=Gamma * GammaStep)
3196 elif event.unicode == u'{':
3197 SetGamma(new_black=BlackLevel - BlackLevelStep)
3198 elif event.unicode == u'}':
3199 SetGamma(new_black=BlackLevel + BlackLevelStep)
3200 elif event.unicode == u'\\':
3201 SetGamma(1.0, 0)
3202 else:
3203 keyfunc = GetPageProp(Pcurrent, 'keys', {}).get(event.unicode, None)
3204 if keyfunc:
3205 SafeCall(keyfunc)
3206 elif IsValidShortcutKey(event.key):
3207 if event.mod & KMOD_SHIFT:
3208 AssignShortcut(Pcurrent, event.key)
3209 else:
3210 # load keyboard shortcut
3211 page = FindShortcut(event.key)
3212 if page and (page != Pcurrent):
3213 TransitionTo(page)
3215 elif event.type == MOUSEBUTTONDOWN:
3216 if VideoPlaying:
3217 Marking = False
3218 Panning = False
3219 return
3220 MouseDownX, MouseDownY = event.pos
3221 if event.button == 1:
3222 MarkUL = MarkLR = MouseToScreen(event.pos)
3223 elif (event.button == 3) and ZoomMode:
3224 PanAnchorX = ZoomX0
3225 PanAnchorY = ZoomY0
3226 elif event.button == 4:
3227 IncrementSpotSize(+8)
3228 elif event.button == 5:
3229 IncrementSpotSize(-8)
3231 elif event.type == MOUSEBUTTONUP:
3232 if VideoPlaying:
3233 StopMPlayer()
3234 DrawCurrentPage()
3235 Marking = False
3236 Panning = False
3237 return
3238 if event.button == 2:
3239 LeaveZoomMode()
3240 DoOverview()
3241 return
3242 if event.button == 1:
3243 if Marking:
3244 # left mouse button released in marking mode -> stop box marking
3245 Marking = False
3246 # reject too small boxes
3247 if (abs(MarkUL[0] - MarkLR[0]) > 0.04) \
3248 and (abs(MarkUL[1] - MarkLR[1]) > 0.03):
3249 boxes = GetPageProp(Pcurrent, 'boxes', [])
3250 oldboxcount = len(boxes)
3251 boxes.append(NormalizeRect(MarkUL[0], MarkUL[1], MarkLR[0], MarkLR[1]))
3252 SetPageProp(Pcurrent, 'boxes', boxes)
3253 if not(oldboxcount) and not(Tracing):
3254 BoxFade(lambda t: t)
3255 DrawCurrentPage()
3256 else:
3257 # left mouse button released, but no marking
3258 LeaveZoomMode()
3259 dest = GetNextPage(Pcurrent, 1)
3260 x, y = event.pos
3261 for valid, target, x0, y0, x1, y1 in GetPageProp(Pcurrent, '_href', []):
3262 if valid and (x >= x0) and (x < x1) and (y >= y0) and (y < y1):
3263 dest = target
3264 break
3265 if type(dest) == types.IntType:
3266 TransitionTo(dest)
3267 else:
3268 RunURL(dest)
3269 if (event.button == 3) and not(Panning):
3270 # right mouse button -> check if a box has to be killed
3271 boxes = GetPageProp(Pcurrent, 'boxes', [])
3272 x, y = MouseToScreen(event.pos)
3273 try:
3274 # if a box is already present around the clicked position, kill it
3275 idx = FindBox(x, y, boxes)
3276 if (len(boxes) == 1) and not(Tracing):
3277 BoxFade(lambda t: 1.0 - t)
3278 del boxes[idx]
3279 SetPageProp(Pcurrent, 'boxes', boxes)
3280 DrawCurrentPage()
3281 except ValueError:
3282 # no box present -> go to previous page
3283 LeaveZoomMode()
3284 TransitionTo(GetNextPage(Pcurrent, -1))
3285 Panning = False
3287 elif event.type == MOUSEMOTION:
3288 pygame.event.clear(MOUSEMOTION)
3289 # mouse move in fullscreen mode -> show mouse cursor and reset mouse timer
3290 if Fullscreen:
3291 pygame.time.set_timer(USEREVENT_HIDE_MOUSE, MouseHideDelay)
3292 SetCursor(True)
3293 # don't react on mouse input during video playback
3294 if VideoPlaying: return
3295 # activate marking if mouse is moved away far enough
3296 if event.buttons[0] and not(Marking):
3297 x, y = event.pos
3298 if (abs(x - MouseDownX) > 4) and (abs(y - MouseDownY) > 4):
3299 Marking = True
3300 # mouse move while marking -> update marking box
3301 if Marking:
3302 MarkLR = MouseToScreen(event.pos)
3303 # mouse move while RMB is pressed -> panning
3304 if event.buttons[2] and ZoomMode:
3305 x, y = event.pos
3306 if not(Panning) and (abs(x - MouseDownX) > 4) and (abs(y - MouseDownY) > 4):
3307 Panning = True
3308 ZoomX0 = PanAnchorX + (MouseDownX - x) * ZoomArea / ScreenWidth
3309 ZoomY0 = PanAnchorY + (MouseDownY - y) * ZoomArea / ScreenHeight
3310 ZoomX0 = min(max(ZoomX0, 0.0), 1.0 - ZoomArea)
3311 ZoomY0 = min(max(ZoomY0, 0.0), 1.0 - ZoomArea)
3312 # if anything changed, redraw the page
3313 if Marking or Tracing or event.buttons[2] or (CursorImage and CursorVisible):
3314 DrawCurrentPage()
3316 elif event.type == USEREVENT_HIDE_MOUSE:
3317 # mouse timer event -> hide fullscreen cursor
3318 pygame.time.set_timer(USEREVENT_HIDE_MOUSE, 0)
3319 SetCursor(False)
3320 DrawCurrentPage()
3322 elif event.type == USEREVENT_PAGE_TIMEOUT:
3323 TransitionTo(GetNextPage(Pcurrent, 1))
3325 elif event.type == USEREVENT_POLL_FILE:
3326 dirty = False
3327 for f in FileProps:
3328 if my_stat(f) != GetFileProp(f, 'stat'):
3329 dirty = True
3330 break
3331 if dirty:
3332 # first, check if the new file is valid
3333 if not os.path.isfile(GetPageProp(Pcurrent, '_file')):
3334 return
3335 # invalidate everything we used to know about the input files
3336 InvalidateCache()
3337 for props in PageProps.itervalues():
3338 for prop in ('_overview_rendered', '_box', '_href'):
3339 if prop in props: del props[prop]
3340 LoadInfoScript()
3341 # force a transition to the current page, reloading it
3342 Pnext=-1
3343 TransitionTo(Pcurrent)
3344 # restart the background renderer thread. this is not completely safe,
3345 # i.e. there's a small chance that we fail to restart the thread, but
3346 # this isn't critical
3347 if CacheMode and BackgroundRendering:
3348 if RTrunning:
3349 RTrestart = True
3350 else:
3351 RTrunning = True
3352 thread.start_new_thread(RenderThread, (Pcurrent, Pnext))
3354 elif event.type == USEREVENT_TIMER_UPDATE:
3355 if TimerTick():
3356 DrawCurrentPage()
3359 ##### FILE LIST GENERATION #####################################################
3361 def IsImageFileName(name):
3362 return os.path.splitext(name)[1].lower() in \
3363 (".jpg", ".jpeg", ".png", ".tif", ".tiff", ".bmp", ".ppm", ".pgm")
3364 def IsPlayable(name):
3365 return IsImageFileName(name) or name.lower().endswith(".pdf") or os.path.isdir(name)
3367 def AddFile(name, title=None):
3368 global FileList, FileName
3370 if os.path.isfile(name):
3371 FileList.append(name)
3372 if title: SetFileProp(name, 'title', title)
3374 elif os.path.isdir(name):
3375 images = [os.path.join(name, f) for f in os.listdir(name) if IsImageFileName(f)]
3376 images.sort(lambda a, b: cmp(a.lower(), b.lower()))
3377 if not images:
3378 print >>sys.stderr, "Warning: no image files in directory `%s'" % name
3379 for img in images: AddFile(img)
3381 elif name.startswith('@') and os.path.isfile(name[1:]):
3382 name = name[1:]
3383 dirname = os.path.dirname(name)
3384 try:
3385 f = file(name, "r")
3386 next_title = None
3387 for line in f:
3388 line = [part.strip() for part in line.split('#', 1)]
3389 if len(line) == 1:
3390 subfile = line[0]
3391 title = None
3392 else:
3393 subfile, title = line
3394 if subfile:
3395 AddFile(os.path.normpath(os.path.join(dirname, subfile)), title)
3396 f.close()
3397 except IOError:
3398 print >>sys.stderr, "Error: cannot read list file `%s'" % name
3399 if not FileName:
3400 FileName = name
3401 else:
3402 FileName = ""
3404 else:
3405 files = list(filter(IsPlayable, glob.glob(name)))
3406 if files:
3407 for f in files: AddFile(f)
3408 else:
3409 print >>sys.stderr, "Error: input file `%s' not found" % name
3412 ##### INITIALIZATION ###########################################################
3414 def main():
3415 global ScreenWidth, ScreenHeight, TexWidth, TexHeight, TexSize, LogoImage
3416 global TexMaxS, TexMaxT, MeshStepX, MeshStepY, EdgeX, EdgeY, PixelX, PixelY
3417 global OverviewGridSize, OverviewCellX, OverviewCellY
3418 global OverviewOfsX, OverviewOfsY, OverviewImage, OverviewPageCount
3419 global OverviewPageMap, OverviewPageMapInv, FileName, FileList, PageCount
3420 global DocumentTitle, PageProps, LogoTexture, OSDFont
3421 global Pcurrent, Pnext, Tcurrent, Tnext, InitialPage
3422 global CacheFile, CacheFileName
3423 global Extensions, AllowExtensions, TextureTarget, PAR, DAR, TempFileName
3424 global BackgroundRendering, FileStats, RTrunning, RTrestart, StartTime
3425 global CursorImage, CursorVisible, InfoScriptPath, PowerdotFix
3427 # allocate temporary file
3428 TempFileName = tempfile.mktemp(prefix="Accentuate-", suffix="_tmp")
3430 # some input guesswork
3431 DocumentTitle = os.path.splitext(os.path.split(FileName)[1])[0]
3432 if FileName and not(FileList):
3433 AddFile(FileName)
3434 if not(FileName) and (len(FileList) == 1):
3435 FileName = FileList[0]
3437 # fill the page list
3438 PageCount = 0
3439 for name in FileList:
3440 ispdf = name.lower().endswith(".pdf")
3441 if ispdf:
3442 # PDF input -> try to pre-parse the PDF file
3443 pages = 0
3444 # phase 1: internal PDF parser
3445 try:
3446 pages, pdf_width, pdf_height = analyze_pdf(name)
3447 if Rotation & 1:
3448 pdf_width, pdf_height = (pdf_height, pdf_width)
3449 res = min(ScreenWidth * 72.0 / pdf_width, \
3450 ScreenHeight * 72.0 / pdf_height)
3451 except:
3452 res = 72.0
3454 # phase 2: use pdftk
3455 try:
3456 assert 0 == spawn(os.P_WAIT, pdftkPath, \
3457 ["pdftk", FileNameEscape + name + FileNameEscape, \
3458 "dump_data", "output", TempFileName + ".txt"])
3459 title, pages = pdftkParse(TempFileName + ".txt", PageCount)
3460 if DocumentTitle and title: DocumentTitle = title
3461 except:
3462 pass
3463 else:
3464 # Image File
3465 pages = 1
3466 SetPageProp(PageCount + 1, '_title', os.path.split(name)[-1])
3468 # validity check
3469 if not pages:
3470 print >>sys.stderr, "Warning: The input file `%s' could not be analyzed." % name
3471 continue
3473 # add pages and files into PageProps and FileProps
3474 pagerange = list(range(PageCount + 1, PageCount + pages + 1))
3475 for page in pagerange:
3476 SetPageProp(page, '_file', name)
3477 if ispdf: SetPageProp(page, '_page', page - PageCount)
3478 title = GetFileProp(name, 'title')
3479 if title: SetPageProp(page, '_title', title)
3480 SetFileProp(name, 'pages', GetFileProp(name, 'pages', []) + pagerange)
3481 SetFileProp(name, 'offsets', GetFileProp(name, 'offsets', []) + [PageCount])
3482 if not GetFileProp(name, 'stat'): SetFileProp(name, 'stat', my_stat(name))
3483 if ispdf: SetFileProp(name, 'res', res)
3484 PageCount += pages
3486 # no pages? strange ...
3487 if not PageCount:
3488 print >>sys.stderr, "The presentation doesn't have any pages, quitting."
3489 sys.exit(1)
3491 # if rendering is wanted, do it NOW
3492 if RenderToDirectory:
3493 sys.exit(DoRender())
3495 # load and execute info script
3496 if not InfoScriptPath:
3497 InfoScriptPath = FileName + ".info"
3498 LoadInfoScript()
3500 # initialize graphics
3501 pygame.init()
3502 if Fullscreen and UseAutoScreenSize:
3503 size = GetScreenSize()
3504 if size:
3505 ScreenWidth, ScreenHeight = size
3506 print >>sys.stderr, "Detected screen size: %dx%d pixels" % (ScreenWidth, ScreenHeight)
3507 flags = OPENGL|DOUBLEBUF
3508 if Fullscreen:
3509 flags |= FULLSCREEN
3510 try:
3511 pygame.display.set_mode((ScreenWidth, ScreenHeight), flags)
3512 except:
3513 print >>sys.stderr, "FATAL: cannot create rendering surface in the desired resolution (%dx%d)" % (ScreenWidth, ScreenHeight)
3514 sys.exit(1)
3515 pygame.display.set_caption(__title__)
3516 pygame.key.set_repeat(500, 30)
3517 if Fullscreen:
3518 pygame.mouse.set_visible(False)
3519 CursorVisible = False
3520 glOrtho(0.0, 1.0, 1.0, 0.0, -10.0, 10.0)
3521 if (Gamma <> 1.0) or (BlackLevel <> 0):
3522 SetGamma(force=True)
3524 # check if graphics are unaccelerated
3525 renderer = glGetString(GL_RENDERER)
3526 print >>sys.stderr, "OpenGL renderer:", renderer
3527 if renderer.lower() in ("mesa glx indirect", "gdi generic"):
3528 print >>sys.stderr, "WARNING: Using an OpenGL software renderer. Accentuate will work, but it will"
3529 print >>sys.stderr, " very likely be too slow to be usable."
3531 # setup the OpenGL texture mode
3532 Extensions = dict([(ext.split('_', 2)[-1], None) for ext in \
3533 glGetString(GL_EXTENSIONS).split()])
3534 if AllowExtensions and ("texture_non_power_of_two" in Extensions):
3535 print >>sys.stderr, "Using GL_ARB_texture_non_power_of_two."
3536 TextureTarget = GL_TEXTURE_2D
3537 TexWidth = ScreenWidth
3538 TexHeight = ScreenHeight
3539 TexMaxS = 1.0
3540 TexMaxT = 1.0
3541 elif AllowExtensions and ("texture_rectangle" in Extensions):
3542 print >>sys.stderr, "Using GL_ARB_texture_rectangle."
3543 TextureTarget = 0x84F5 # GL_TEXTURE_RECTANGLE_ARB
3544 TexWidth = ScreenWidth
3545 TexHeight = ScreenHeight
3546 TexMaxS = ScreenWidth
3547 TexMaxT = ScreenHeight
3548 else:
3549 print >>sys.stderr, "Using conventional power-of-two textures with padding."
3550 TextureTarget = GL_TEXTURE_2D
3551 TexWidth = npot(ScreenWidth)
3552 TexHeight = npot(ScreenHeight)
3553 TexMaxS = ScreenWidth * 1.0 / TexWidth
3554 TexMaxT = ScreenHeight * 1.0 / TexHeight
3555 TexSize = TexWidth * TexHeight * 3
3557 # set up some variables
3558 if DAR is not None:
3559 PAR = DAR / float(ScreenWidth) * float(ScreenHeight)
3560 MeshStepX = 1.0 / MeshResX
3561 MeshStepY = 1.0 / MeshResY
3562 PixelX = 1.0 / ScreenWidth
3563 PixelY = 1.0 / ScreenHeight
3564 EdgeX = BoxEdgeSize * 1.0 / ScreenWidth
3565 EdgeY = BoxEdgeSize * 1.0 / ScreenHeight
3566 if InitialPage is None:
3567 InitialPage = GetNextPage(0, 1)
3568 Pcurrent = InitialPage
3570 # prepare logo image
3571 LogoImage = Image.open(StringIO.StringIO(LOGO))
3572 LogoTexture = glGenTextures(1)
3573 glBindTexture(GL_TEXTURE_2D, LogoTexture)
3574 glTexImage2D(GL_TEXTURE_2D, 0, 1, 256, 64, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, LogoImage.tostring())
3575 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
3576 DrawLogo()
3577 pygame.display.flip()
3579 # initialize OSD font
3580 try:
3581 OSDFont = GLFont(FontTextureWidth, FontTextureHeight, FontList, FontSize, search_path=FontPath)
3582 DrawLogo()
3583 titles = []
3584 for key in ('title', '_title'):
3585 titles.extend([p[key] for p in PageProps.itervalues() if key in p])
3586 if titles:
3587 OSDFont.AddString("".join(titles))
3588 except ValueError:
3589 print >>sys.stderr, "The OSD font size is too large, the OSD will be rendered incompletely."
3590 except IOError:
3591 print >>sys.stderr, "Could not open OSD font file, disabling OSD."
3592 except (NameError, AttributeError, TypeError):
3593 print >>sys.stderr, "Your version of PIL is too old or incomplete, disabling OSD."
3595 # initialize mouse cursor
3596 if CursorImage:
3597 try:
3598 CursorImage = PrepareCustomCursor(Image.open(CursorImage))
3599 except:
3600 print >>sys.stderr, "Could not open the mouse cursor image, using standard cursor."
3601 CursorImage = False
3603 # set up page cache
3604 if CacheMode == PersistentCache:
3605 if not CacheFileName:
3606 CacheFileName = FileName + ".cache"
3607 InitPCache()
3608 if CacheMode == FileCache:
3609 CacheFile = tempfile.TemporaryFile(prefix="Accentuate-", suffix=".cache")
3611 # initialize overview metadata
3612 OverviewPageMap=[i for i in xrange(1, PageCount + 1) \
3613 if GetPageProp(i, ('overview', '_overview'), True) \
3614 and (i >= PageRangeStart) and (i <= PageRangeEnd)]
3615 OverviewPageCount = max(len(OverviewPageMap), 1)
3616 OverviewPageMapInv = {}
3617 for page in xrange(1, PageCount + 1):
3618 OverviewPageMapInv[page] = len(OverviewPageMap) - 1
3619 for i in xrange(len(OverviewPageMap)):
3620 if OverviewPageMap[i] >= page:
3621 OverviewPageMapInv[page] = i
3622 break
3624 # initialize overview page geometry
3625 OverviewGridSize = 1
3626 while OverviewPageCount > OverviewGridSize * OverviewGridSize:
3627 OverviewGridSize += 1
3628 OverviewCellX = int(ScreenWidth / OverviewGridSize)
3629 OverviewCellY = int(ScreenHeight / OverviewGridSize)
3630 OverviewOfsX = int((ScreenWidth - OverviewCellX * OverviewGridSize)/2)
3631 OverviewOfsY = int((ScreenHeight - OverviewCellY * \
3632 int((OverviewPageCount + OverviewGridSize - 1) / OverviewGridSize)) / 2)
3633 OverviewImage = Image.new('RGB', (TexWidth, TexHeight))
3635 # fill overlay "dummy" images
3636 dummy = LogoImage.copy()
3637 maxsize = (OverviewCellX - 2 * OverviewBorder, OverviewCellY - 2 * OverviewBorder)
3638 if (dummy.size[0] > maxsize[0]) or (dummy.size[1] > maxsize[1]):
3639 dummy.thumbnail(ZoomToFit(dummy.size, maxsize), Image.ANTIALIAS)
3640 margX = int((OverviewCellX - dummy.size[0]) / 2)
3641 margY = int((OverviewCellY - dummy.size[1]) / 2)
3642 dummy = dummy.convert(mode='RGB')
3643 for page in range(OverviewPageCount):
3644 pos = OverviewPos(page)
3645 OverviewImage.paste(dummy, (pos[0] + margX, pos[1] + margY))
3646 del dummy
3648 # set up background rendering
3649 if not EnableBackgroundRendering:
3650 print >>sys.stderr, "Background rendering isn't available on this platform."
3651 BackgroundRendering = False
3653 # if caching is enabled, pre-render all pages
3654 if CacheMode and not(BackgroundRendering):
3655 DrawLogo()
3656 DrawProgress(0.0)
3657 pygame.display.flip()
3658 for pdf in FileProps:
3659 if pdf.lower().endswith(".pdf"):
3660 ParsePDF(pdf)
3661 stop = False
3662 progress = 0.0
3663 for page in range(InitialPage, PageCount + 1) + range(1, InitialPage):
3664 event = pygame.event.poll()
3665 while event.type != NOEVENT:
3666 if event.type == KEYDOWN:
3667 if (event.key == K_ESCAPE) or (event.unicode == u'q'):
3668 Quit()
3669 stop = True
3670 elif event.type == MOUSEBUTTONUP:
3671 stop = True
3672 event = pygame.event.poll()
3673 if stop: break
3674 if (page >= PageRangeStart) and (page <= PageRangeEnd):
3675 PageImage(page)
3676 DrawLogo()
3677 progress += 1.0 / PageCount;
3678 DrawProgress(progress)
3679 pygame.display.flip()
3681 # create buffer textures
3682 DrawLogo()
3683 pygame.display.flip()
3684 glEnable(TextureTarget)
3685 Tcurrent = glGenTextures(1)
3686 Tnext = glGenTextures(1)
3687 for T in (Tcurrent, Tnext):
3688 glBindTexture(TextureTarget, T)
3689 glTexParameteri(TextureTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
3690 glTexParameteri(TextureTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
3691 glTexParameteri(TextureTarget, GL_TEXTURE_WRAP_S, GL_CLAMP)
3692 glTexParameteri(TextureTarget, GL_TEXTURE_WRAP_T, GL_CLAMP)
3694 # prebuffer current and next page
3695 Pnext = 0
3696 RenderPage(Pcurrent, Tcurrent)
3697 PageEntered(update_time=False)
3698 PreloadNextPage(GetNextPage(Pcurrent, 1))
3700 # some other preparations
3701 PrepareTransitions()
3702 GenerateSpotMesh()
3703 if PollInterval:
3704 pygame.time.set_timer(USEREVENT_POLL_FILE, PollInterval * 1000)
3706 # start the background rendering thread
3707 if CacheMode and BackgroundRendering:
3708 RTrunning = True
3709 thread.start_new_thread(RenderThread, (Pcurrent, Pnext))
3711 # start output and enter main loop
3712 StartTime = pygame.time.get_ticks()
3713 pygame.time.set_timer(USEREVENT_TIMER_UPDATE, 100)
3714 if not(Fullscreen) and CursorImage:
3715 pygame.mouse.set_visible(False)
3716 DrawCurrentPage()
3717 UpdateCaption(Pcurrent)
3719 # Kick off LIRC thread
3720 if(UseLIRC is True):
3721 irrec = IRRec()
3722 irrec.start()
3724 while True:
3725 HandleEvent(pygame.event.wait())
3728 # wrapper around main() that ensures proper uninitialization
3729 def run_main():
3730 global CacheFile
3731 try:
3732 main()
3733 finally:
3734 StopMPlayer()
3735 # ensure that background rendering is halted
3736 Lrender.acquire()
3737 Lcache.acquire()
3738 # remove all temp files
3739 if 'CacheFile' in globals():
3740 del CacheFile
3741 for tmp in glob.glob(TempFileName + "*"):
3742 try:
3743 os.remove(tmp)
3744 except OSError:
3745 pass
3746 pygame.quit()
3747 if(UseLIRC is True):
3748 pylirc.exit()
3751 ##### COMMAND-LINE PARSER AND HELP #############################################
3753 def if_op(cond, res_then, res_else):
3754 if cond: return res_then
3755 else: return res_else
3757 def HelpExit(code=0):
3758 print """A nice presentation tool.
3760 Usage: """+os.path.basename(sys.argv[0])+""" [OPTION...] <INPUT(S)...>
3762 You may either play a PDF file, a directory containing image files or
3763 individual image files.
3765 Input options:
3766 -r, --rotate <n> rotate pages clockwise in 90-degree steps
3767 --scale scale images to fit screen (not used in PDF mode)
3768 --supersample use supersampling (only used in PDF mode)
3769 -s --supersample for PDF files, --scale for image files
3770 -I, --script <path> set the path of the info script
3771 -u, --poll <seconds> check periodically if the source file has been
3772 updated and reload it if it did
3773 -o, --output <dir> don't display the presentation, only render to .png
3774 -h, --help show this help text and exit
3776 Output options:
3777 -f, --fullscreen """+if_op(Fullscreen,"do NOT ","")+"""start in fullscreen mode
3778 -g, --geometry <WxH> set window size or fullscreen resolution
3779 -A, --aspect <X:Y> adjust for a specific display aspect ratio (e.g. 5:4)
3780 -G, --gamma <G[:BL]> specify startup gamma and black level
3782 Page options:
3783 -i, --initialpage <n> start with page <n>
3784 -p, --pages <A-B> only cache pages in the specified range;
3785 implicitly sets -i <A>
3786 -w, --wrap go back to the first page after the last page
3787 -J, --fade fade to black after the last page
3788 -H, --hyperlink attempt to compensate for landscaped hyperlinks (hack)
3789 -a, --auto <seconds> automatically advance to next page after some seconds
3790 -O, --autooverview <x> automatically derive page visibility on overview page
3791 -O first = show pages with captions
3792 -O last = show pages before pages with captions
3794 Display options:
3795 -t, --transition <trans[,trans2...]>
3796 force a specific transitions or set of transitions
3797 -l, --listtrans print a list of available transitions and exit
3798 -F, --font <file> use a specific TrueType font file for the OSD
3799 -S, --fontsize <px> specify the OSD font size in pixels
3800 -C, --cursor <F[:X,Y]> use a .png image as the mouse cursor
3801 -L, --layout <spec> set the OSD layout (please read the documentation)
3803 Timing options:
3804 -M, --minutes display time in minutes, not seconds
3805 -d, --duration <time> set the desired duration of the presentation and show
3806 a progress bar at the bottom of the screen
3807 -T, --transtime <ms> set transition duration in milliseconds
3808 -D, --mousedelay <ms> set mouse hide delay for fullscreen mode (in ms)
3809 -B, --boxfade <ms> set highlight box fade duration in milliseconds
3810 -Z, --zoom <ms> set zoom duration in milliseconds
3812 Advanced options:
3813 -c, --cache <mode> set page cache mode:
3814 -c none = disable caching completely
3815 -c memory = store cache in RAM
3816 -c disk = store cache on disk temporarily
3817 -c persistent = store cache on disk persistently
3818 --cachefile <path> set the persistent cache file path (implies -cp)
3819 -b, --noback don't pre-render images in the background
3820 -P, --gspath <path> set path to GhostScript or pdftoppm executable
3821 -R, --meshres <XxY> set mesh resolution for effects (default: 48x36)
3822 -e, --noext don't use OpenGL texture size extensions
3824 For detailed information, visit""", __website__
3825 sys.exit(code)
3827 def ListTransitions():
3828 print "Available transitions:"
3829 standard = dict([(tc.__name__, None) for tc in AvailableTransitions])
3830 trans = [(tc.__name__, tc.__doc__) for tc in AllTransitions]
3831 trans.append(('None', "no transition"))
3832 trans.sort()
3833 maxlen = max([len(item[0]) for item in trans])
3834 for name, desc in trans:
3835 if name in standard:
3836 star = '*'
3837 else:
3838 star = ' '
3839 print star, name.ljust(maxlen), '-', desc
3840 print "(transitions with * are enabled by default)"
3841 sys.exit(0)
3843 def TryTime(s, regexp, func):
3844 m = re.match(regexp, s, re.I)
3845 if not m: return 0
3846 return func(map(int, m.groups()))
3847 def ParseTime(s):
3848 return TryTime(s, r'([0-9]+)s?$', lambda m: m[0]) \
3849 or TryTime(s, r'([0-9]+)m$', lambda m: m[0] * 60) \
3850 or TryTime(s, r'([0-9]+)[m:]([0-9]+)[ms]?$', lambda m: m[0] * 60 + m[1]) \
3851 or TryTime(s, r'([0-9]+)[h:]([0-9]+)[hm]?$', lambda m: m[0] * 3600 + m[1] * 60) \
3852 or TryTime(s, r'([0-9]+)[h:]([0-9]+)[m:]([0-9]+)s?$', lambda m: m[0] * 3600 + m[1] * 60 + m[2])
3854 def opterr(msg):
3855 print >>sys.stderr, "command line parse error:", msg
3856 print >>sys.stderr, "use `%s -h' to get help" % sys.argv[0]
3857 print >>sys.stderr, "or visit", __website__, "for full documentation"
3858 sys.exit(2)
3860 def SetTransitions(list):
3861 global AvailableTransitions
3862 index = dict([(tc.__name__.lower(), tc) for tc in AllTransitions])
3863 index['none'] = None
3864 AvailableTransitions=[]
3865 for trans in list.split(','):
3866 try:
3867 AvailableTransitions.append(index[trans.lower()])
3868 except KeyError:
3869 opterr("unknown transition `%s'" % trans)
3871 def ParseLayoutPosition(value):
3872 xpos = []
3873 ypos = []
3874 for c in value.strip().lower():
3875 if c == 't': ypos.append(0)
3876 elif c == 'b': ypos.append(1)
3877 elif c == 'l': xpos.append(0)
3878 elif c == 'r': xpos.append(1)
3879 elif c == 'c': xpos.append(2)
3880 else: opterr("invalid position specification `%s'" % value)
3881 if not xpos: opterr("position `%s' lacks X component" % value)
3882 if not ypos: opterr("position `%s' lacks Y component" % value)
3883 if len(xpos)>1: opterr("position `%s' has multiple X components" % value)
3884 if len(ypos)>1: opterr("position `%s' has multiple Y components" % value)
3885 return (xpos[0] << 1) | ypos[0]
3886 def SetLayoutSubSpec(key, value):
3887 global OSDTimePos, OSDTitlePos, OSDPagePos, OSDStatusPos
3888 global OSDAlpha, OSDMargin
3889 lkey = key.strip().lower()
3890 if lkey in ('a', 'alpha', 'opacity'):
3891 try:
3892 OSDAlpha = float(value)
3893 except ValueError:
3894 opterr("invalid alpha value `%s'" % value)
3895 if OSDAlpha > 1.0:
3896 OSDAlpha *= 0.01 # accept percentages, too
3897 if (OSDAlpha < 0.0) or (OSDAlpha > 1.0):
3898 opterr("alpha value %s out of range" % value)
3899 elif lkey in ('margin', 'dist', 'distance'):
3900 try:
3901 OSDMargin = float(value)
3902 except ValueError:
3903 opterr("invalid margin value `%s'" % value)
3904 if OSDMargin < 0:
3905 opterr("margin value %s out of range" % value)
3906 elif lkey in ('t', 'time'):
3907 OSDTimePos = ParseLayoutPosition(value)
3908 elif lkey in ('title', 'caption'):
3909 OSDTitlePos = ParseLayoutPosition(value)
3910 elif lkey in ('page', 'number'):
3911 OSDPagePos = ParseLayoutPosition(value)
3912 elif lkey in ('status', 'info'):
3913 OSDStatusPos = ParseLayoutPosition(value)
3914 else:
3915 opterr("unknown layout element `%s'" % key)
3916 def SetLayout(spec):
3917 for sub in spec.replace(':', '=').split(','):
3918 try:
3919 key, value = sub.split('=')
3920 except ValueError:
3921 opterr("invalid layout spec `%s'" % sub)
3922 SetLayoutSubSpec(key, value)
3924 def ParseCacheMode(arg):
3925 arg = arg.strip().lower()
3926 if "none".startswith(arg): return NoCache
3927 if "off".startswith(arg): return NoCache
3928 if "memory".startswith(arg): return MemCache
3929 if "disk".startswith(arg): return FileCache
3930 if "file".startswith(arg): return FileCache
3931 if "persistent".startswith(arg): return PersistentCache
3932 opterr("invalid cache mode `%s'" % arg)
3934 def ParseAutoOverview(arg):
3935 arg = arg.strip().lower()
3936 if "off".startswith(arg): return Off
3937 if "first".startswith(arg): return First
3938 if "last".startswith(arg): return Last
3939 try:
3940 i = int(arg)
3941 assert (i >= Off) and (i <= Last)
3942 except:
3943 opterr("invalid auto-overview mode `%s'" % arg)
3945 def ParseOptions(argv):
3946 global FileName, FileList, Fullscreen, Scaling, Supersample, CacheMode
3947 global TransitionDuration, MouseHideDelay, BoxFadeDuration, ZoomDuration
3948 global ScreenWidth, ScreenHeight, MeshResX, MeshResY, InitialPage, Wrap
3949 global AutoAdvance, RenderToDirectory, Rotation, AllowExtensions, DAR
3950 global BackgroundRendering, UseAutoScreenSize, PollInterval, CacheFileName
3951 global PageRangeStart, PageRangeEnd, FontList, FontSize, Gamma, BlackLevel
3952 global EstimatedDuration, CursorImage, CursorHotspot, MinutesOnly
3953 global GhostScriptPath, pdftoppmPath, UseGhostScript, InfoScriptPath
3954 global AutoOverview, FadeToBlackAtEnd, PowerdotFix
3956 try: # unused short options: jknqvxyzEKNQUVWXY
3957 opts, args = getopt.getopt(argv, \
3958 "hfg:sc:i:wa:t:lo:r:T:D:B:Z:P:R:eA:mbp:u:F:S:G:d:C:ML:I:O:J:H", \
3959 ["help", "fullscreen", "geometry=", "scale", "supersample", \
3960 "nocache", "initialpage=", "wrap", "auto", "listtrans", "output=", \
3961 "rotate=", "transition=", "transtime=", "mousedelay=", "boxfade=", \
3962 "zoom=", "gspath=", "meshres=", "noext", "aspect", "memcache", \
3963 "noback", "pages=", "poll=", "font=", "fontsize=", "gamma=",
3964 "duration=", "cursor=", "minutes", "layout=", "script=", "cache=",
3965 "cachefile=", "autooverview=", "fade", "hyperlink"])
3966 except getopt.GetoptError, message:
3967 opterr(message)
3969 for opt, arg in opts:
3970 if opt in ("-h", "--help"):
3971 HelpExit()
3972 if opt in ("-l", "--listtrans"):
3973 ListTransitions()
3974 if opt in ("-f", "--fullscreen"):
3975 Fullscreen = not(Fullscreen)
3976 if opt in ("-e", "--noext"):
3977 AllowExtensions = not(AllowExtensions)
3978 if opt in ("-s", "--scale"):
3979 Scaling = not(Scaling)
3980 if opt in ("-s", "--supersample"):
3981 Supersample = 2
3982 if opt in ("-w", "--wrap"):
3983 Wrap = not(Wrap)
3984 if opt in ("-J", "--fade"):
3985 FadeToBlackAtEnd = not(FadeToBlackAtEnd)
3986 if opt in ("-H", "--hyperlink"):
3987 PowerdotFix = not(PowerdotFix)
3988 if opt in ("-O", "--autooverview"):
3989 AutoOverview = ParseAutoOverview(arg)
3990 if opt in ("-c", "--cache"):
3991 CacheMode = ParseCacheMode(arg)
3992 if opt == "--nocache":
3993 print >>sys.stderr, "Note: The `--nocache' option is deprecated, use `--cache none' instead."
3994 CacheMode = NoCache
3995 if opt in ("-m", "--memcache"):
3996 print >>sys.stderr, "Note: The `--memcache' option is deprecated, use `--cache memory' instead."
3997 CacheMode = MemCache
3998 if opt == "--cachefile":
3999 CacheFileName = arg
4000 CacheMode = PersistentCache
4001 if opt in ("-M", "--minutes"):
4002 MinutesOnly = not(MinutesOnly)
4003 if opt in ("-b", "--noback"):
4004 BackgroundRendering = not(BackgroundRendering)
4005 if opt in ("-t", "--transition"):
4006 SetTransitions(arg)
4007 if opt in ("-L", "--layout"):
4008 SetLayout(arg)
4009 if opt in ("-o", "--output"):
4010 RenderToDirectory = arg
4011 if opt in ("-I", "--script"):
4012 InfoScriptPath = arg
4013 if opt in ("-F", "--font"):
4014 FontList = [arg]
4015 if opt in ("-P", "--gspath"):
4016 UseGhostScript = (arg.replace("\\", "/").split("/")[-1].lower().find("pdftoppm") < 0)
4017 if UseGhostScript:
4018 GhostScriptPath = arg
4019 else:
4020 pdftoppmPath = arg
4021 if opt in ("-S", "--fontsize"):
4022 try:
4023 FontSize = int(arg)
4024 assert FontSize > 0
4025 except:
4026 opterr("invalid parameter for --fontsize")
4027 if opt in ("-i", "--initialpage"):
4028 try:
4029 InitialPage = int(arg)
4030 assert InitialPage > 0
4031 except:
4032 opterr("invalid parameter for --initialpage")
4033 if opt in ("-d", "--duration"):
4034 try:
4035 EstimatedDuration = ParseTime(arg)
4036 assert EstimatedDuration > 0
4037 except:
4038 opterr("invalid parameter for --duration")
4039 if opt in ("-a", "--auto"):
4040 try:
4041 AutoAdvance = int(arg) * 1000
4042 assert (AutoAdvance > 0) and (AutoAdvance <= 86400000)
4043 except:
4044 opterr("invalid parameter for --auto")
4045 if opt in ("-T", "--transtime"):
4046 try:
4047 TransitionDuration = int(arg)
4048 assert (TransitionDuration >= 0) and (TransitionDuration < 32768)
4049 except:
4050 opterr("invalid parameter for --transtime")
4051 if opt in ("-D", "--mousedelay"):
4052 try:
4053 MouseHideDelay = int(arg)
4054 assert (MouseHideDelay >= 0) and (MouseHideDelay < 32768)
4055 except:
4056 opterr("invalid parameter for --mousedelay")
4057 if opt in ("-B", "--boxfade"):
4058 try:
4059 BoxFadeDuration = int(arg)
4060 assert (BoxFadeDuration >= 0) and (BoxFadeDuration < 32768)
4061 except:
4062 opterr("invalid parameter for --boxfade")
4063 if opt in ("-Z", "--zoom"):
4064 try:
4065 ZoomDuration = int(arg)
4066 assert (ZoomDuration >= 0) and (ZoomDuration < 32768)
4067 except:
4068 opterr("invalid parameter for --zoom")
4069 if opt in ("-r", "--rotate"):
4070 try:
4071 Rotation = int(arg)
4072 except:
4073 opterr("invalid parameter for --rotate")
4074 while Rotation < 0: Rotation += 4
4075 Rotation = Rotation & 3
4076 if opt in ("-u", "--poll"):
4077 try:
4078 PollInterval = int(arg)
4079 assert PollInterval >= 0
4080 except:
4081 opterr("invalid parameter for --poll")
4082 if opt in ("-g", "--geometry"):
4083 try:
4084 ScreenWidth, ScreenHeight = map(int, arg.split("x"))
4085 assert (ScreenWidth >= 320) and (ScreenWidth < 4096)
4086 assert (ScreenHeight >= 200) and (ScreenHeight < 4096)
4087 UseAutoScreenSize = False
4088 except:
4089 opterr("invalid parameter for --geometry")
4090 if opt in ("-R", "--meshres"):
4091 try:
4092 MeshResX, MeshResY = map(int, arg.split("x"))
4093 assert (MeshResX > 0) and (MeshResX <= ScreenWidth)
4094 assert (MeshResY > 0) and (MeshResY <= ScreenHeight)
4095 except:
4096 opterr("invalid parameter for --meshres")
4097 if opt in ("-p", "--pages"):
4098 try:
4099 PageRangeStart, PageRangeEnd = map(int, arg.split("-"))
4100 assert PageRangeStart > 0
4101 assert PageRangeStart <= PageRangeEnd
4102 except:
4103 opterr("invalid parameter for --pages")
4104 InitialPage=PageRangeStart
4105 if opt in ("-A", "--aspect"):
4106 try:
4107 if ':' in arg:
4108 fx, fy = map(float, arg.split(':'))
4109 DAR = fx / fy
4110 else:
4111 DAR = float(arg)
4112 assert DAR > 0.0
4113 except:
4114 opterr("invalid parameter for --aspect")
4115 if opt in ("-G", "--gamma"):
4116 try:
4117 if ':' in arg:
4118 arg, bl = arg.split(':', 1)
4119 BlackLevel = int(bl)
4120 Gamma = float(arg)
4121 assert Gamma > 0.0
4122 assert (BlackLevel >= 0) and (BlackLevel < 255)
4123 except:
4124 opterr("invalid parameter for --gamma")
4125 if opt in ("-C", "--cursor"):
4126 try:
4127 if ':' in arg:
4128 arg = arg.split(':')
4129 assert len(arg) > 1
4130 CursorImage = ':'.join(arg[:-1])
4131 CursorHotspot = map(int, arg[-1].split(','))
4132 else:
4133 CursorImage = arg
4134 assert (BlackLevel >= 0) and (BlackLevel < 255)
4135 except:
4136 opterr("invalid parameter for --cursor")
4138 for arg in args:
4139 AddFile(arg)
4140 if not FileList:
4141 opterr("no playable files specified")
4142 return
4144 # glob and filter argument list
4145 files = []
4146 for arg in args:
4147 files.extend(glob.glob(arg))
4148 files = list(filter(IsPlayable, files))
4150 # if only one argument is specified, use it as the informal file name
4151 if len(files) == 1:
4152 FileName = files[0]
4153 else:
4154 FileName = ""
4156 # construct final FileList by expanding directories to image file lists
4157 FileList = []
4158 for item in files:
4159 if os.path.isdir(item):
4160 images = [os.path.join(item, f) for f in os.listdir(item) if IsImageFileName(f)]
4161 images.sort(lambda a, b: cmp(a.lower(), b.lower()))
4162 FileList.extend(images)
4163 else:
4164 FileList.append(item)
4166 if not FileList:
4167 opterr("no playable files specified")
4170 # use this function if you intend to use Accentuate as a library
4171 def run():
4172 try:
4173 run_main()
4174 except SystemExit, e:
4175 return e.code
4177 if __name__=="__main__":
4178 ParseOptions(sys.argv[1:])
4179 run_main()