8a5b8d63e837ca31398f00044a4ac2d592f3562a
[joel/kofoto.git] / src / packages / kofoto / output / woolly.py
1 # pylint: disable-msg=C0301
2
3 '''Implementation of the output module "woolly".'''
4
5 import os
6 import re
7 from kofoto.outputengine import OutputEngine
8
9 css = u'''
10 html {
11     color: #000000;
12     background: #dddddd;
13     font-family: Verdana, Geneva, Arial, Helvetica, sans-serif;
14     font-size: 10pt;
15 }
16
17 img {
18     border-style: none;
19     display: block;
20 }
21
22 img.thinborder {
23     color: #000000; /* Netscape */
24     border-color: #000000; /* IE */
25     border-style: solid;
26     border-width: 1px;
27 }
28
29 img.toc {
30     color: #000000; /* Netscape */
31     border-color: #000000; /* IE */
32     border-style: solid;
33     border-width: 1px;
34     margin-top: 0.3cm;
35 }
36
37 img.icon {
38     display: inline;
39 }
40
41 a.toc {
42     display: block;
43 }
44
45 td.arrow {
46     font-size: 9pt;
47 }
48
49 a:link {
50     color: #24238e;
51 }
52
53 a:visited {
54     color: #6b4789;
55 }
56
57 .textleft {
58     padding-right: 10pt;
59 }
60
61 .header {
62     font-size : 10pt;
63     background: #c5c2c0;
64     table-layout: fixed;
65     border-style: solid;
66     border-width: 1px;
67     border-color: #000000;
68 }
69
70 .photographer {
71     font-size: 9pt;
72 }
73
74 .footer {
75     font-size : 9pt;
76 }
77
78 h1 {
79     font-size : 15pt;
80     margin-bottom: 10px;
81 }
82
83 h2 {
84     font-size: 10pt;
85 }
86
87 td.info {
88     background: #c5c2c0;
89     border-style: solid;
90     border-width: 1px;
91     border-color: #000000;
92 }
93 '''
94
95 transparent_1x1_png = '\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x01\x03\x00\x00\x00%\xdbV\xca\x00\x00\x00\x03PLTE\xff\xff\xff\xa7\xc4\x1b\xc8\x00\x00\x00\x01tRNS\x00@\xe6\xd8f\x00\x00\x00\x01bKGD\x00\x88\x05\x1dH\x00\x00\x00\nIDATx\xdac`\x00\x00\x00\x02\x00\x01\xe5\'\xde\xfc\x00\x00\x00\x00IEND\xaeB`\x82'
96
97 frame_bottom_png = "\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x11\x08\x06\x00\x00\x00\x1c\xc3\xc6\x12\x00\x00\x001IDATx\xda\x8d\xc9A\r\x000\x0c\x03\xb1S\x07\'$\x072*\x8abHGa\xfe\x1aI\xc6vH\xb2\x05P\xdd\xcd\x99\x99\x8b\xed\xfd\x8cE\x92\x01\xf4\x00#%(P\xc8\xc2\x05\'\x00\x00\x00\x00IEND\xaeB`\x82"
98
99 frame_bottomleft_png = '\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x0c\x00\x00\x00\x11\x08\x03\x00\x00\x00\xde\xe3\xbd\x90\x00\x00\x00?PLTE\xff\xff\xffVVV\xd4\xd4\xd4\xfe\xfe\xfe\xdb\xdb\xdbNNN+++\xff\xff\xff\xf8\xf8\xf8UUU\xfd\xfd\xfd\xb1\xb1\xb1\xd3\xd3\xd3OOO999GGG222###\xb0\xb0\xb0\x07\x07\x07***]\xe6\x12X\x00\x00\x00\x01tRNS\x00@\xe6\xd8f\x00\x00\x00WIDATx\xdau\xcbI\x12\x80 \x0cD\xd1f\x08\x88\xa2\x12\xe0\xfegUIQ\xc6\x85o\xf7+\x1d\x18\xeb\x9c\xa70\xe0\x8e\xb8x;\x8c\x88N \xd9\xd5M\xa0-\xdb\tfO\x86\x0e2\x0f\x0c\xa7\x9c%H\x1e%JV\xc1U\xcd\x1a\xf7\x10\n\xe1\xc5UE\xcf\x7f\xf1\x995\x86v\x01N\xe0\x02\xcf\xcf?\xc7w\x00\x00\x00\x00IEND\xaeB`\x82'
100
101 frame_bottomright_png = "\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x11\x00\x00\x00\x11\x08\x06\x00\x00\x00;mG\xfa\x00\x00\x00\xd2IDATx\xda\xa5\x931\n\x840\x10E\x7f\x96@\xbc\x84\x95\x9d\xb9\x8b\xa5\x17\xd9\xde\xc2\xde\xdb\x05\x1b\x89U\x0e\xb1c\x91\xbf\xcd\xba\xac\xacY\x8d;\x10Bf\xe0\xc1<\xf2\xd14\xcdc\x9a&\xc6\x18\x19c\xa4s\x8em\xdb\xd29w\xd4\x8b\xd6Z\x07\xc0\xde\x86a0eYB)\x85\xab\xa5\xab\xaa\xfa\x0b\x00\x00\x1a\x00Hb\xef>\r\x19\xc7\xf1\xfd \x89y\x9e!\"y\x90\xbe\xef7\x90eYP\x14\x05\x8c1\xe7!]\xd7}5\x8d1Xe\x9fYM\xd7u\xbd;\xc8\x91\xadS\x83\x1c\xd9\x1b\xb1)\xd8\x91\xec\x8d\xd8\x14\xe4H\xf6\xae\xd8\xbd\xfa%;)6G\xb6\xce\xf9T\x9f\x92\xd7\xb3\x8aenVH\xc2{O\x11!\x00\xaaW\x9c\xb3!\"\xc2\x10\x82\x17\x91\xbb\x02`/\x86\x97\x00\x04@x\x02\xb7\x89\xa5\xf8\x0b\xe9\xfe\xce\x00\x00\x00\x00IEND\xaeB`\x82"
102
103 frame_left_png = "\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x06\x00\x00\x00\x01\x08\x06\x00\x00\x00\xfd\xc9\xdf\xf0\x00\x00\x00\x1fIDATx\xda\x05\xc1\x01\x01\x00\x00\x08\xc20\xfb\xd0\x87\xe4\xcfs\xdc\xae-\x80\xea\xd4\x01&\xe1\x01\xe4\xbc\x12\x12\xcbp\xe9\x92\x00\x00\x00\x00IEND\xaeB`\x82"
104
105 frame_right_png = "\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x11\x00\x00\x00\x01\x08\x06\x00\x00\x008\xbbEa\x00\x00\x00(IDATx\xda\x85\xc8A\x01\x000\x10\xc20\xfc\xe0\xe7\x94WO\x99\x84\xe5\x99\xb4\x05P\x9d:`w7`\x9f\xb3-I\xfa\x00N\xd33\x8ef\xbf\xfbs\x00\x00\x00\x00IEND\xaeB`\x82"
106
107 frame_top_png = "\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x06\x08\x06\x00\x00\x00\x02\x10\xf41\x00\x00\x00$IDATx\xda\x05\xc1\x01\x11\x00\x00\x08\x02\xb1?\xeb\xd8\x87\xe4\x1cq\xd0\rI\xc6vi{\x030I\x8e\xdd\xf5\x03\xe1\xef\x0e$\xd4\n|5\x00\x00\x00\x00IEND\xaeB`\x82"
108
109 frame_topleft_png = "\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x06\x00\x00\x00\x06\x08\x06\x00\x00\x00\xe0\xcc\xefH\x00\x00\x00\x06bKGD\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\x00\x00\tpHYs\x00\x00\x0b\x12\x00\x00\x0b\x12\x01\xd2\xdd~\xfc\x00\x00\x00\x07tIME\x07\xd3\x05\x01\x0e3\x1b\\\xb2\xad\xe2\x00\x00\x00TIDATx\x9c]\x89\xb1\r\xc0 \x10\x03m\x84`\x0f\xbe\"\x03\xb1\x00#\xb0Xv\xfa%pE*\xa2\x90\x93\\\xf8\x8e\xad\xb5[\x92\xa5\x94H\x12\x9b8\xe7\xb4\xde\xfbUJ9C\xce\x99f\xc6Z\xeb\x11\xc2>$\xdf\x01@\xc4\x0fIpw\x84\xaf\\k\xc1\xdd1\xc6\xd0\x03E\xd4\x18G\xcb:X\x17\x00\x00\x00\x00IEND\xaeB`\x82"
110
111 frame_toprightlower_png = '\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x11\x00\x00\x00\x06\x08\x03\x00\x00\x00\x12`\x85\xeb\x00\x00\x00BPLTE\xff\xff\xffVVVUUUOOO\xd4\xd4\xd4\xd3\xd3\xd3\xb1\xb1\xb1NNN\xfe\xfe\xfe\xfd\xfd\xfd\xff\xff\xff\xdb\xdb\xdb\xf8\xf8\xf8999HHH222###\x07\x07\x07+++\xb0\xb0\xb0***$$$\xfe%C\x19\x00\x00\x00\x01tRNS\x00@\xe6\xd8f\x00\x00\x000IDATx\xdac\x10b\xe1\xe0`agce\x15\x16`\x80\x00\xb0\x08\x13\x0b\'\'\xab\x08\xb2\x08#\x88\x10"B\x84_TDD@\x10Y\x04j<\x00\x81\xe6\x02\x97\xe5V\x02\x01\x00\x00\x00\x00IEND\xaeB`\x82'
112
113 frame_toprightupper_png = '\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x11\x00\x00\x00\x06\x08\x03\x00\x00\x00\x12`\x85\xeb\x00\x00\x00BPLTE\xff\xff\xffVVVUUUOOO\xd4\xd4\xd4\xd3\xd3\xd3\xb1\xb1\xb1NNN\xfe\xfe\xfe\xfd\xfd\xfd\xff\xff\xff\xdb\xdb\xdb\xf8\xf8\xf8999HHH222###\x07\x07\x07+++\xb0\xb0\xb0***$$$\xfe%C\x19\x00\x00\x00\x01tRNS\x00@\xe6\xd8f\x00\x00\x007IDATx\xdaU\xcbK\x0e\x00\x10\x10\x04QC3\xed\xcf\xfd/Kbcj\xf9\x92r"\xe2\x83\xb8/\x001\xe9/$3\xbc\x91Rh\xa5\xb6+\xe6\xd2J\xa2\x0f\x9dk?8-\x01\x01%!\xee\xc5\x0e\x00\x00\x00\x00IEND\xaeB`\x82'
114
115 previous_png = '\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x18\x00\x00\x00\x18\x08\x03\x00\x00\x00\xd7\xa9\xcd\xca\x00\x00\x01;PLTE\xff\xff\xff\x00\x00\x00\x10\x00\x1d\x04\x00\x07\x1b\x134CCn\x17\x03&\x16\x112P_\x91ov\xa6 \x0e-\x13\x0f2GZ\x8f\x83\x92\xca\x85\x8a\xba7+J$\x110#\x101#\x10/\x1f\x0b*\x06\x00\t\x16\x0f>GY\x92~\x8e\xc9\x97\xa0\xd8\x97\x9d\xd4\x8d\x8e\xc0\x8c\x8c\xbb\x8a\x8a\xbb\x89\x89\xba\x88\x88\xb9\x8b\x8b\xbauq\x9c\x17\x0e3PY\x91\x7f\x8e\xc8\x92\x9d\xd5\x97\x9e\xd6\x96\x9f\xd7\x98\x9f\xd7\x98\xa1\xd7\x99\xa0\xd8}}\xb2 \r.\n\x04)7I\x84n\x7f\xbf\x84\x92\xcf\x87\x95\xd0\x86\x94\xcf\x86\x95\xcf\x84\x94\xcf\x83\x93\xce\x85\x93\xd0\x84\x90\xcflp\xa9\x1a\n+\x00\x00\r\x0e\x17O1M\x96Db\xafIf\xb2Jh\xb3Ic\xb0Jd\xb1BM\x95\x11\x00)\x03\x00!\x12"c&?\x91-J\x9c.K\x9f.H\x9f-H\x9e0J\x9f/>\x8a\r\x00%\x10\x1ea";\x8d*D\x99+E\x9a)D\x9a(B\x99+B\x9a,;\x87\x0b\x00#%=\x94\x1c.\x7f\x19+z\x1d/|!,n\x08\x00\x1d!9\x8a\x1a/}\x08\x030\x04\x00\x1b\x07\x00\x19\x00\x00\x03\x0e\x1c_\x13"n\x04\x00\x19\x03\x00\x1f\t\x10F\x01\x00\x11\x00\x00\x01\x9e\xfe\x9a\x98\x00\x00\x00\x01tRNS\x00@\xe6\xd8f\x00\x00\x00\xc8IDATx\xdac`\xa0#`\xc4!\xce\xc4\x8c]\x9c\x85\x95\r\xab8;\x07\'\x176qn\x1e^>~\x01\x01AA!!\x01 \x06\x02a\x11\x90\xb8\xa8\x98\xb8\x84\xa4\x94\xb4\xb4\x8c\xac\x9c<\x98\x90\x95S\x10\x06\x8a+*)\xabH\xa8\xaa\xa9khhh\x82\t\r\ru-m\x06\x06\x1d]=}\x03\x03\x03C#c\x13}S0a\xa2ofn\xc1`iemck\x07\x04\xb6@`\x0f%\x1c\x1c\x9d\x18\x18\x9c]\\\xdd\xdc!\xc0\xc3\xd3\x03Lxxy\xfb\x00\xedp\xf6\xf5\xf3\x0f\x08\x0cB\x06\xc1!\xa1\x0c\x10\x99\xa0\xb0\xf0\x08$\x10\x19\x15\xcd\x00\x91\x89\x89\x8d\x8bG\x06\t\x89\x10\x1f:\'%\xa7`\x0f\xab\xd4\xb4t\x1c\xa1k\x99Aj|P\x01\x00\x00\x8d\xc20#(\x85\x17q\x00\x00\x00\x00IEND\xaeB`\x82'
116
117 noprevious_png = '\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x18\x00\x00\x00\x18\x08\x03\x00\x00\x00\xd7\xa9\xcd\xca\x00\x00\x00\x9cPLTE\xff\xff\xff\x7f\x7f\x7f\xac\xac\xac\x96\x96\x96\xbf\xbf\xbf\x89\x89\x89\xa0\xa0\xa0\xc9\xc9\xc9\xba\xba\xba\x84\x84\x84\x9c\x9c\x9c\xc6\xc6\xc6\x8f\x8f\x8f\xa5\xa5\xa5\xcf\xcf\xcf\xb0\xb0\xb0\x82\x82\x82\xa3\xa3\xa3\x87\x87\x87\xaf\xaf\xaf\x8b\x8b\x8b\x9e\x9e\x9e\x91\x91\x91\xa8\xa8\xa8\xb2\xb2\xb2\x81\x81\x81\x98\x98\x98\xa2\xa2\xa2\xcb\xcb\xcb\xbc\xbc\xbc\xc8\xc8\xc8\xd1\xd1\xd1\x80\x80\x80\xad\xad\xad\x97\x97\x97\xc0\xc0\xc0\x8a\x8a\x8a\xa1\xa1\xa1\xca\xca\xca\x85\x85\x85\x9d\x9d\x9d\xc7\xc7\xc7\x90\x90\x90\xd0\xd0\xd0\xb1\xb1\xb1\x83\x83\x83\xa4\xa4\xa4\x88\x88\x88\x8c\x8c\x8c\x9f\x9f\x9f\x92\x92\x92\xb3\xb3\xb38\x0cY\x9f\x00\x00\x00\x01tRNS\x00@\xe6\xd8f\x00\x00\x00\xaeIDATx\xda\xbd\x91K\x0f\x820\x10\x84\x17\xb4.X\xf1\x11\xa3\xd2\x82<\x14\xd0\nT\xd0\xff\xff\xdf,\x8f\x03$\xe5`b\xfc\x0es\x98\xd9\xec&\xb3\x00\x7f\xc4\x98\xf0\xcbH\xefW\xbb\x9b\xd6O\xf6\xe7D\xe7\x13\x13)\xaf\x86\x90vsb\x8a0\xc7 \x10\x94\x8aV(\xb5H3\x1f\x8bu\x98\xe7\xe1\x90\xab\xdal\xcb9\xfa\xbe\x9fe\x19b\'\x88h=!\xaa\x9c\x8d\xfbV\xb8\x8a\xa2\x97\xe2d\x03\xb0c-\x9d\x0e)e+\xd2Y\x94\xea\x06{\xdc=\xcfKG\x1c\x9a\xa0I\xd2\x9a\xcf\x06\xf0\xcb\x16\xbad\xc9m6\xa2\xaf\x8e\xad^L\xdf\x15#\x13%Bd|\xfb\x8f\x1f\xf0\x01g\xef\x16\xef\xde#\x0cr\x00\x00\x00\x00IEND\xaeB`\x82'
118
119 next_png = '\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x18\x00\x00\x00\x18\x08\x03\x00\x00\x00\xd7\xa9\xcd\xca\x00\x00\x01;PLTE\xff\xff\xff\x00\x00\x00\x04\x00\x07\x10\x00\x1d\x17\x03&CCn\x1b\x134 \x0e-ov\xa6P_\x91\x16\x112\x06\x00\t\x1f\x0b*#\x10/#\x101$\x1107+J\x85\x8a\xba\x83\x92\xcaGZ\x8f\x13\x0f2uq\x9c\x88\x88\xb9\x89\x89\xba\x8b\x8b\xba\x8a\x8a\xbb\x8c\x8c\xbb\x8d\x8e\xc0\x97\x9d\xd4\x97\xa0\xd8~\x8e\xc9GY\x92\x16\x0f> \r.}}\xb2\x98\x9f\xd7\x98\xa1\xd7\x99\xa0\xd8\x96\x9f\xd7\x97\x9e\xd6\x92\x9d\xd5\x7f\x8e\xc8PY\x91\x17\x0e3\x1a\n+lp\xa9\x84\x90\xcf\x84\x92\xcf\x83\x93\xce\x85\x93\xd0\x84\x94\xcf\x86\x95\xcf\x86\x94\xcf\x87\x95\xd0n\x7f\xbf7I\x84\n\x04)\x11\x00)BM\x95Jd\xb1If\xb2Ic\xb0Jh\xb3Db\xaf1M\x96\x0e\x17O\x00\x00\r\r\x00%/>\x8a0J\x9f.H\x9f-H\x9e.K\x9f-J\x9c&?\x91\x12"c\x03\x00!\x0b\x00#,;\x87+B\x9a(B\x99)D\x9a+E\x9a*D\x99";\x8d\x10\x1ea\x08\x00\x1d!,n\x1d/|\x19+z\x1c.\x7f%=\x94\x00\x00\x03\x07\x00\x19\x04\x00\x1b\x08\x030\x1a/}!9\x8a\x04\x00\x19\x13"n\x0e\x1c_\x01\x00\x11\t\x10F\x03\x00\x1f\x00\x00\x017\xd36x\x00\x00\x00\x01tRNS\x00@\xe6\xd8f\x00\x00\x00\xc8IDATx\xdac`\xa0?`\xc4%\xc1\xc4\x8cC\x82\x85\x95\r\xbb\x04;\x07\'\x17\x84\xc5\xcd\xc3\x0b\x02|\xfc@\xcc\xc7\xcf/ ($,\x02\x96\xe0\x11\x15\x13\x97\x10\x13\x97\x94\x02\x13R\xd22\xb2r\xf2\n \tE%e\x15\x15U\x15\x15\x15e0\xa1\xa6.\xab\xa1\xa9\xa5\r\x94\xd0\xd1\xd5\xd370\xd4702\x06\x13&\xa6\xa6\xa6\xfaf\xe6\x16\x0c\x0c\x96V\xd666\xb666P\xc2\x0e\x08l\xec\x1d\x1c\x9d\x18\x9c]\\\xdd\xdc\xdc\xdd<<\xc0\x04\x18xzy\xfb00\xf8\xfa\xf9\x07 \x83\xc0\xa0\xe0\x90P\xa08CXxD$\x12\x88\x8a\x0e\x80\x883\xc4\xc4\xc6!\x83\xf8\x84D\x888:HJN\xc1*\xce\x90\x9a\x96\x8e=\xac2\x9cH\x8d\x0f\n\x00\x00-\xb5/\xc3\xf0)\x85[\x00\x00\x00\x00IEND\xaeB`\x82'
120
121 nonext_png = '\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x18\x00\x00\x00\x18\x08\x03\x00\x00\x00\xd7\xa9\xcd\xca\x00\x00\x00\x9cPLTE\xff\xff\xff\x7f\x7f\x7f\xac\xac\xac\x96\x96\x96\xbf\xbf\xbf\x89\x89\x89\xa0\xa0\xa0\xc9\xc9\xc9\xba\xba\xba\x84\x84\x84\x9c\x9c\x9c\xc6\xc6\xc6\x8f\x8f\x8f\xa5\xa5\xa5\xcf\xcf\xcf\xb0\xb0\xb0\x82\x82\x82\xa3\xa3\xa3\x87\x87\x87\xaf\xaf\xaf\x8b\x8b\x8b\x9e\x9e\x9e\x91\x91\x91\xa8\xa8\xa8\xb2\xb2\xb2\x81\x81\x81\x98\x98\x98\xa2\xa2\xa2\xcb\xcb\xcb\xbc\xbc\xbc\xc8\xc8\xc8\xd1\xd1\xd1\x80\x80\x80\xad\xad\xad\x97\x97\x97\xc0\xc0\xc0\x8a\x8a\x8a\xa1\xa1\xa1\xca\xca\xca\x85\x85\x85\x9d\x9d\x9d\xc7\xc7\xc7\x90\x90\x90\xd0\xd0\xd0\xb1\xb1\xb1\x83\x83\x83\xa4\xa4\xa4\x88\x88\x88\x8c\x8c\x8c\x9f\x9f\x9f\x92\x92\x92\xb3\xb3\xb38\x0cY\x9f\x00\x00\x00\x01tRNS\x00@\xe6\xd8f\x00\x00\x00\xa9IDATx\xda\xbd\x91\xcd\x0e\x820\x10\x84\x17\xb4\x96Z\xebO\x8cJ\x0b\x02jA\x11\x8b\xa0\xef\xffn\xd6\xc2\x01\x0c=\x18\x13\xbfL\xe60\x93\xb9\xec\x02\xfc\x1f\xc7V$\xcaR\x9cW\xd5p!\xf7k\xd9nQ\xd5EP\xec"S \x8f\xd2\\+2\x16\xe1"\xce]\xb3\x91\xa7\xb8KQ\xc4\xf3\xfc\xf0\xde\xdc=\x8cq\xa6\x955\x16\x86!\x1e\x97\x04\x80\xecn\xbe\xaf\xd5\xdaS\xe3/X\x95\x80\x9a\xb0R\xc3\x981CYo9\x80\xda\xa4=\x82 \xb8\\u\x0e\xcb\xa3\x18u\x10u\xda\xe4\xe0\xf0\x1eDL\x9b\xfc\x13\xfe\x98\xf1\xe1#"n9{\xf2\xed?~\xe0\x05|9\x16\xef\xde\x00\xf7B\x00\x00\x00\x00IEND\xaeB`\x82'
122
123 smaller_png = '\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x18\x00\x00\x00\x18\x08\x03\x00\x00\x00\xd7\xa9\xcd\xca\x00\x00\x01ePLTE\xff\xff\xff\x1e\x1e9\x1a\x1a1\x18\x18-\x16\x16)\x1b\x1b2\x1b\x1b4IIxZZ\xb5ii\xc6hh\xc3WW\xb0@@m\x12\x12#\x10\x10\x1f\x16\x16+<<g}}\xd6\x83\x83\xda\x82\x82\xd9||\xd5uu\xd0__\xba..Q\n\n\x15\x14\x14%JJ{\x87\x87\xdc\x90\x90\xe1\x9d\x9d\xe8\xa6\xa6\xed\xaa\xaa\xef\xa8\xa8\xed\x9a\x9a\xe7\x85\x85\xdcqq\xcc::c\x08\x08\x11\x13\x13$88a\x80\x80\xd7\x8b\x8b\xde\xad\xad\xf0\xb3\xb3\xf2\xb5\xb5\xf2\xb1\xb1\xf0\x95\x95\xe6vv\xd1\'\'H\x11\x11"nn\xc9\x89\x89\xde\xb1\xb1\xf2\xb6\xb6\xf3\xa8\xa8\xef\xa2\xa2\xeb\x98\x98\xe7bb\xbd\x07\x07\x0f\x0f\x0f\x1eEEr\xb8\xb8\xf3\xa4\xa4\xed\x9b\x9b\xe855\\\x05\x05\n\x0b\x0b\x16``\xbb\x8b\x8b\xe0\xac\xac\xefVV\xaf\x03\x03\x06\t\t\x12yy\xd2\x93\x93\xe4((I))J\xa3\xa3\xectt\xcdzz\xd3\x97\x97\xe6\'\'F\x9f\x9f\xea\x95\x95\xe4pp\xc9\x02\x02\x05\x05\x05\x0b]]\xb8\x85\x85\xdaRR\xa5\x01\x01\x02;;d\x8d\x8d\xe0\xa1\xa1\xea\x9d\x9d\xea\x88\x88\xdd\x00\x00\x00\x8e\x8e\xe1\x7f\x7f\xd6##@11V{{\xd4\x81\x81\xd8mm\xc8\x01\x01\x03\x1c\x1c5TT\xadqq\xcakk\xc6RR\x9f\x18\x18/\x00\x00\x01KK\x82UU\xaeJJ}$$AAAnKK\x80""?\xdf\xa4f\xa0\x00\x00\x00\x01tRNS\x00@\xe6\xd8f\x00\x00\x01;IDATx\xdac` \x00\x18\x99\x98YXX0\x84Y\xd9\xd898\xb9\xb8yx\xf9P\xc5\xf9\x058\x05\x85\x84\x84ED\xc5\xc4%\x90\xc5%\xa5\x04\xa5ed\xe5\xe4\x15\x14\x95\x94UT\x11\xe2j\xea\x1a\x9a\x8aZ\xda:\xda\xbaZrz\xfa\x06\x08\x19C#cY\x13\x1dSS\x1d\x1d]3s\x0bK+\x98\xb8\xb5\x8d\x92\x9e\x96\xb6\xa9\xad\xad\xa9\x8e\x89\x96\x9d\xbd\x8c\x83#T\xc2\xc9\xd9\xc5N\xcbD\x07$\xa1\xeb\n\x94ps\x87Jxxz\xc9iy{\xfb\xf8\xf8x\x1b\x18\xf8*\xca\xf8\xc1$\xac\xfc\x03\xcc\xcd\x0c\xbc\x81\xc0\xc0 0(X3$\x14*\x11\x16\xae\'\xeb\xab\xe0\xaa\xa5\xa5%/gn!\x13\x11\x19\x05\x95p\x8c\x8e\xd1\xb3\x8f\xb5\x93\x93\xb3\xf3\x8d\xb3\x90\x89\x17\x14O\x809\xcb\x91\xd3E&@\xd1\xde^6\xc0+1>\x89#\n\xee\x0f\xf7d\r\xa5\xf8D\x99D\x19\x17c!\x11e\xd6\x04\x84\xd7CS\xf4S\x934\xd2\x92\x92D\xf4\xd3}\x12\x90\x03+#3+[\x19\x08rr\xf3\x12\x12Pd\xf23\x02\x0b\n\x0b\x8b\x8a\x81\xa2\thR\t\t0\x11^^T\x19\x84\x92\x12F\\2\xa5e8\xf5\x18b\x97`\xc0\xa1\x01\x02\x00\\\x80@\x88\x10gev\x00\x00\x00\x00IEND\xaeB`\x82'
124
125 nosmaller_png = '\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x18\x00\x00\x00\x18\x08\x03\x00\x00\x00\xd7\xa9\xcd\xca\x00\x00\x00\xf3PLTE\xff\xff\xff\x7f\x7f\x7f\xaf\xaf\xaf\x97\x97\x97\xc7\xc7\xc7\xbb\xbb\xbb\xa4\xa4\xa4\xd3\xd3\xd3\x8b\x8b\x8b\x85\x85\x85\xb5\xb5\xb5\x9d\x9d\x9d\xcd\xcd\xcd\xc1\xc1\xc1\xd9\xd9\xd9\xac\xac\xac\x92\x92\x92\x82\x82\x82\xb3\xb3\xb3\x9a\x9a\x9a\xbe\xbe\xbe\xd6\xd6\xd6\x8e\x8e\x8e\x88\x88\x88\xb8\xb8\xb8\xa1\xa1\xa1\xd0\xd0\xd0\xc4\xc4\xc4\xdc\xdc\xdc\xca\xca\xca\xa7\xa7\xa7\x94\x94\x94\x81\x81\x81\xb1\xb1\xb1\x99\x99\x99\xc9\xc9\xc9\xbd\xbd\xbd\xa6\xa6\xa6\xd5\xd5\xd5\x8d\x8d\x8d\x87\x87\x87\x9f\x9f\x9f\xcf\xcf\xcf\xc3\xc3\xc3\xdb\xdb\xdb\xae\xae\xae\x84\x84\x84\x9c\x9c\x9c\xc0\xc0\xc0\xd8\xd8\xd8\x90\x90\x90\x8a\x8a\x8a\xba\xba\xba\xd2\xd2\xd2\xc6\xc6\xc6\xde\xde\xde\x80\x80\x80\x98\x98\x98\xc8\xc8\xc8\xbc\xbc\xbc\xd4\xd4\xd4\x8c\x8c\x8c\x9e\x9e\x9e\xce\xce\xce\xc2\xc2\xc2\xad\xad\xad\x93\x93\x93\x83\x83\x83\xb4\xb4\xb4\xbf\xbf\xbf\xd7\xd7\xd7\x8f\x8f\x8f\x89\x89\x89\xb9\xb9\xb9\xa2\xa2\xa2\xd1\xd1\xd1\xc5\xc5\xc5\xdd\xdd\xdd\xcb\xcb\xcb\xa8\xa8\xa8\x95\x95\x95\xec#\\\x17\x00\x00\x00\x01tRNS\x00@\xe6\xd8f\x00\x00\x01\x15IDATx\xda\x85\x90kS\x82@\x14@Y\'\x0bKI\xaa\x85\x02cu\xa0-yTN\x81\x88\x18\x94\x82\x10\xb8\xf9\xff\x7fM\x94<\xa4b:\x1f\xcf\xb9wf\xefR\xd4?\xaco\x174M\xff\xd2\x1cw3\x8a/[W\x11_\xf7\xf48\xf6\xd34\xf5\x83\xd5\xac\xb3\xef\x9f$\xff\x80\x98p\x12\n\x96\xf2\xb2\xac|t"\xca\x023\xd8\x0ct\x06z\x01\xaa\n\xdfvL}\xf3\x85\x1e\xba\xeaq\x19\xee\x0e-/\x9b\x9fN\xb3\xc0\xe0\x84<\xb0y\xe8\xacd\xcc\xe8Uh\r\xf3p\xff\xd8\x85\xcc\xf6\x1b\x84\xb0@.\x8a\xb0\xec\xa9n\x88v\x1e\x1dy\xf2\xdc\xce\x03{\xe6\x998d2&\xd0U\x89\xf5Z\x86\xf1\xb5\x97\xb8\x18B\x8cM\x958\xfe\x0c\x14\xcfbc\x99\xa8B\x92\x98j\x978\xda\xc8.\xef\x18\xf6E\xcb!\x19\xb2\x93\xfa\n\x07\xaa\xd3\xed\xf3\xa0\xa7\x89\xa2\xa6\xf9A{\x0b\xf6?\xcb\xe6\x8c\xb9\x92\xf1|\xba\x00\xa0V\x80\x8d>\x0cCz\xcb,\xf8\x99@a\xa2\xa8^\xaa\x91\xf7uS\x91\xfa\x8d;\xfc\xdf\x81jX\xd8\xf1\t\xda/.\x11\x05E\x18\xfa\x00\x00\x00\x00IEND\xaeB`\x82'
126
127 larger_png = '\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x18\x00\x00\x00\x18\x08\x03\x00\x00\x00\xd7\xa9\xcd\xca\x00\x00\x01hPLTE\xff\xff\xff\x1e\x1e9\x1a\x1a1\x18\x18-\x16\x16)\x1b\x1b2\x1b\x1b4IIxZZ\xb5ii\xc6hh\xc3WW\xb0@@m\x12\x12#\x10\x10\x1f\x16\x16+<<g}}\xd6\x83\x83\xda\x82\x82\xd9||\xd5uu\xd0__\xba..Q\n\n\x15\x14\x14%JJ{\x87\x87\xdc\x90\x90\xe1\x9d\x9d\xe8\xa6\xa6\xed\xaa\xaa\xef\xa8\xa8\xed\x9a\x9a\xe7\x85\x85\xdcqq\xcc::c\x08\x08\x11\x13\x13$88a\x80\x80\xd7\x8b\x8b\xde\xad\xad\xf0\xb3\xb3\xf2\xb5\xb5\xf2\xb1\xb1\xf0\x95\x95\xe6vv\xd1\'\'H\x11\x11"nn\xc9\x89\x89\xde\xb1\xb1\xf2\xb6\xb6\xf3))J\xa8\xa8\xef\xa2\xa2\xeb\x98\x98\xe7bb\xbd\x07\x07\x0f\x0f\x0f\x1eEEr\xb8\xb8\xf3\xa4\xa4\xed\x9b\x9b\xe855\\\x05\x05\n\x0b\x0b\x16``\xbb\x8b\x8b\xe0\xac\xac\xefVV\xaf\x03\x03\x06\t\t\x12yy\xd2\x93\x93\xe4((I\xa3\xa3\xectt\xcdzz\xd3\x97\x97\xe6\'\'F\x9f\x9f\xea\x95\x95\xe4pp\xc9\x02\x02\x05\x05\x05\x0b]]\xb8\x85\x85\xdaRR\xa5\x01\x01\x02;;d\x8d\x8d\xe0\xa1\xa1\xea\x9d\x9d\xea\x88\x88\xdd\x00\x00\x00%%D\x8e\x8e\xe1\x7f\x7f\xd6##@11V{{\xd4\x81\x81\xd8mm\xc8\x01\x01\x03\x1c\x1c5TT\xadqq\xcakk\xc6RR\x9f\x18\x18/\x00\x00\x01KK\x82UU\xaeJJ}$$AAAnKK\x80""?S\xe4\xb0\xf5\x00\x00\x00\x01tRNS\x00@\xe6\xd8f\x00\x00\x01:IDATx\xdac` \x00\x18\x99\x98YXX0\x84Y\xd9\xd898\xb9\xb8yx\xf9P\xc5\xf9\x058\x05\x85\x84\x84ED\xc5\xc4%\x90\xc5%\xa5\x04\xa5ed\xe5\xe4\x15\x14\x95\x94UT\x11\xe2j\xea\x1a\x9a\x8aZ\xda:\xda\xbaZrz\xfa\x06\x08\x19C#cY\x13\x1dS33\x1d]s\x0bK+k\x98\xb8\x8d\xad\x92\x9e\x96\xb6\xa9\x1dP\xc2D\xcb\xdeA\xc6\xd1\t*\xe1\xec\xe2j\xafe\xa2\x03\x92\xd0u\x03J\xb8{@%<\xbd\xbc\xe5\xb4||\xcc\xcc\xcc|\x0c\x0c|\x15e\xfc`\x12\xd6\xfe\x01\x16\xe6\x06>@``\x10\x18\x14\xac\x19\x12\n\x95\x08\x0b\xd7\x93\xf5Up\xd320\x90\x97\xb3\xb0\x94\x89\x88\x8c\x82J8E\xc7\xe89\xc4\xda\xcb\x05\x06\xfa\xc6Y\xca\xc4\x0b\x8a\'\xc0\x9c\xe5\xc4\xe9*\x13\xa0\xe8\x90\x98\x18\xe0\x9d\x14\x9f\xcc\x11\x05\xf7\x87G\x8a\x86R|\x92L\x92\x8c\xab\xb1\x90\x882k\x02\xc2\xeb\xa1\xa9\xfai\xc9\x1a\xe9\xc9\xc9"\xfa\x19f\t\xc8\x81\x95\x99\x95\x9d\xa3\x0c\x04\xb9y\xf9\t\t(2\x05\x99\x81\x85EE\xc5%@\xd1\x044\xa9\x84\x04\x98\x08//\xaa\x0cBI)#.\x99\xb2r\x9cz\x0c\xb1K0\xe0\xd0\x00\x01\x00\x0e7A6+\xddR\n\x00\x00\x00\x00IEND\xaeB`\x82'
128
129 nolarger_png = '\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x18\x00\x00\x00\x18\x08\x03\x00\x00\x00\xd7\xa9\xcd\xca\x00\x00\x00\xf3PLTE\xff\xff\xff\x7f\x7f\x7f\xaf\xaf\xaf\x97\x97\x97\xc7\xc7\xc7\xbb\xbb\xbb\xa4\xa4\xa4\xd3\xd3\xd3\x8b\x8b\x8b\x85\x85\x85\xb5\xb5\xb5\x9d\x9d\x9d\xcd\xcd\xcd\xc1\xc1\xc1\xd9\xd9\xd9\xac\xac\xac\x92\x92\x92\x82\x82\x82\xb3\xb3\xb3\x9a\x9a\x9a\xbe\xbe\xbe\xd6\xd6\xd6\x8e\x8e\x8e\x88\x88\x88\xb8\xb8\xb8\xa1\xa1\xa1\xd0\xd0\xd0\xc4\xc4\xc4\xdc\xdc\xdc\xca\xca\xca\xa7\xa7\xa7\x94\x94\x94\x81\x81\x81\xb1\xb1\xb1\x99\x99\x99\xc9\xc9\xc9\xbd\xbd\xbd\xa6\xa6\xa6\xd5\xd5\xd5\x8d\x8d\x8d\x87\x87\x87\x9f\x9f\x9f\xcf\xcf\xcf\xc3\xc3\xc3\xdb\xdb\xdb\xae\xae\xae\x84\x84\x84\x9c\x9c\x9c\xc0\xc0\xc0\xd8\xd8\xd8\x90\x90\x90\x8a\x8a\x8a\xba\xba\xba\xd2\xd2\xd2\xc6\xc6\xc6\xde\xde\xde\x80\x80\x80\x98\x98\x98\xc8\xc8\xc8\xbc\xbc\xbc\xd4\xd4\xd4\x8c\x8c\x8c\x9e\x9e\x9e\xce\xce\xce\xc2\xc2\xc2\xad\xad\xad\x93\x93\x93\x83\x83\x83\xb4\xb4\xb4\xbf\xbf\xbf\xd7\xd7\xd7\x8f\x8f\x8f\x89\x89\x89\xb9\xb9\xb9\xa2\xa2\xa2\xd1\xd1\xd1\xc5\xc5\xc5\xdd\xdd\xdd\xcb\xcb\xcb\xa8\xa8\xa8\x95\x95\x95\xec#\\\x17\x00\x00\x00\x01tRNS\x00@\xe6\xd8f\x00\x00\x01\x18IDATx\xda\x85\xd0\xedR\x82@\x18\x05`\xd6\xc9\xc2\xd2M*\xa0\xc0X\x1d\x88\x92\x8f\xca)\x08\x11\x83P\x10\x026\xee\xffj\xa2\xe4C*\xa7\xf3\xf3<\xef\xbb\xb3\xbb\x04\xf1O6\xb7K\x92$\x7f\xd5\x0cs3\x89/;W\x11\xdb\xee\xc9i\xec\xa5i\xea\x05\xebyo\xb7\x7f\x12\xbc\x03l\xd0\xb3\x903\xa5\xd7U\xd3G\'\xbc\xc8\xc1Q6\xd2 \xed\x06\xa8\x11\xb6k\x1bZ\x96\xe5y\xa6\x85\x8er\\\xc3\xdd\xa1\xe9\x16\xf3/_\x00\xe5\x04?P%\xf4\xd6\xa2\x0c\xb5\x06:\xe3\x12\xee\x1f\xfb4\xcc\xbf\x83\x90\xcc\xe1\x8b\nV\x03\xc5\t\xd1\xb6GG\xae\xb8\xb0J\xa0\xce\\C\x0e!DhF;\n6\xdfj\x98^\xbb\x89#\xd3\xc59\x86\x82mo\x0e\xaakQ\xb1\x88\x15.\xf1}\xa5\x8fmub\xd5\xef\x18\x0fy\xd3\xc6ED;\xf5$\x064O\xb7\xce\x83\x81\xca\xf3\xaa\xea\x05\xdd\x1c\xec~\x96\xc5\xe8\x0b\xa9\xc8\xf3\xe9\x12\x80\x96\x00\x0b}\xe8\xba\xe0\x17-\xf8I\xa0j\xa2\xa8-\xcd\xc8\xfbf\x9f\x08\xc3\xbd;\xec\xdf@\xecY\xd8\xe6\x13m\xda.x\xed\x89\x8d\x00\x00\x00\x00\x00IEND\xaeB`\x82'
130
131 nextalbum_png = '\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x18\x00\x00\x00\x11\x08\x03\x00\x00\x00\xf0\xa6\x9c\x02\x00\x00\x01;PLTE\x00\x00\xd3\x00\x00\x00\x04\x00\x07\x10\x00\x1d\x17\x03&CCn\x1b\x134 \x0e-ov\xa6P_\x91\x16\x112\x06\x00\t\x1f\x0b*#\x10/#\x101$\x1107+J\x85\x8a\xba\x83\x92\xcaGZ\x8f\x13\x0f2uq\x9c\x88\x88\xb9\x89\x89\xba\x8b\x8b\xba\x8a\x8a\xbb\x8c\x8c\xbb\x8d\x8e\xc0\x97\x9d\xd4\x97\xa0\xd8~\x8e\xc9GY\x92\x16\x0f> \r.}}\xb2\x98\x9f\xd7\x98\xa1\xd7\x99\xa0\xd8\x96\x9f\xd7\x97\x9e\xd6\x92\x9d\xd5\x7f\x8e\xc8PY\x91\x17\x0e3\x1a\n+lp\xa9\x84\x90\xcf\x84\x92\xcf\x83\x93\xce\x85\x93\xd0\x84\x94\xcf\x86\x95\xcf\x86\x94\xcf\x87\x95\xd0n\x7f\xbf7I\x84\n\x04)\x11\x00)BM\x95Jd\xb1If\xb2Ic\xb0Jh\xb3Db\xaf1M\x96\x0e\x17O\x00\x00\r\r\x00%/>\x8a0J\x9f.H\x9f-H\x9e.K\x9f-J\x9c&?\x91\x12"c\x03\x00!\x0b\x00#,;\x87+B\x9a(B\x99)D\x9a+E\x9a*D\x99";\x8d\x10\x1ea\x08\x00\x1d!,n\x1d/|\x19+z\x1c.\x7f%=\x94\x00\x00\x03\x07\x00\x19\x04\x00\x1b\x08\x030\x1a/}!9\x8a\x04\x00\x19\x13"n\x0e\x1c_\x01\x00\x11\t\x10F\x03\x00\x1f\x00\x00\x01\x8b\x8fZv\x00\x00\x00\x01tRNS\x00@\xe6\xd8f\x00\x00\x00\x01bKGD\x00\x88\x05\x1dH\x00\x00\x00\tpHYs\x00\x00\x0b\x12\x00\x00\x0b\x12\x01\xd2\xdd~\xfc\x00\x00\x00\x07tIME\x07\xd4\x01\x02\x1367\xfd\xd3<\xf1\x00\x00\x00\xf5IDATx\x9cc`@\x05\x8c\x0c8\x00\x133\x0e\t\x16V6\xec\x12\xec\x1c\x9c\\\x10\x167\x0f/\x08\xf0\xf1\x031\x1f?\xbf\x80\xa0\x90\xb0\x08X\x82GTL\\BL\\R\nLHI\xcb\xc8\xca\xc9+\x80$\x14\x95\x94UTTUTT\x94\xc1\x84\x9a\xba\xac\x86\xa6\x966PBGRE\xdf\xc0P\xdf\xc0\xc8\x18L\x98\x98\x9a\x9a\xea\x9b\x99[00\xf0K\xaa\x18\xd9\xd8\x82\x81\r\x10\xd8\x01\x81\x8d\xbd\x83\xa3\x13\x03\x9f\xa4\x8a\x81\x8d;\x18\xb8y@\x80\xa7\x97\xb7\x0f\x03\x03\xaf\xb8\x8a\xbe\x8d[\x00\x1c\x04\x06\x05\x87\x84\x02\xc5\x19x\xc5T\rm\xdd\x03"\xa1 *:\x00"\xce\xc0+\x014\xca- 2\x0e\x02\xe2\x13\x12!\xe20\xa3"\xe3 \xfeMJN\x81\x88\x03\x8dR\xd6\xb3v\xf5\x8f\x80J\xa4\xa6\xa5C\x03\x87GTI\xd7\xca\xc5/<\x16\xc2\xcdp\x82\x85\x1a7\x8f\xa2\x8e\xa5\xb3oX\x0cZ|\x00\x00\x1d\xb33\x1e\x92T\x9aL\x00\x00\x00\x00IEND\xaeB`\x82'
132
133 previousalbum_png = '\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x18\x00\x00\x00\x11\x08\x03\x00\x00\x00\xf0\xa6\x9c\x02\x00\x00\x01;PLTE\x00\x00\xd3\x00\x00\x00\x04\x00\x07\x10\x00\x1d\x17\x03&CCn\x1b\x134 \x0e-ov\xa6P_\x91\x16\x112\x06\x00\t\x1f\x0b*#\x10/#\x101$\x1107+J\x85\x8a\xba\x83\x92\xcaGZ\x8f\x13\x0f2uq\x9c\x88\x88\xb9\x89\x89\xba\x8b\x8b\xba\x8a\x8a\xbb\x8c\x8c\xbb\x8d\x8e\xc0\x97\x9d\xd4\x97\xa0\xd8~\x8e\xc9GY\x92\x16\x0f> \r.}}\xb2\x98\x9f\xd7\x98\xa1\xd7\x99\xa0\xd8\x96\x9f\xd7\x97\x9e\xd6\x92\x9d\xd5\x7f\x8e\xc8PY\x91\x17\x0e3\x1a\n+lp\xa9\x84\x90\xcf\x84\x92\xcf\x83\x93\xce\x85\x93\xd0\x84\x94\xcf\x86\x95\xcf\x86\x94\xcf\x87\x95\xd0n\x7f\xbf7I\x84\n\x04)\x11\x00)BM\x95Jd\xb1If\xb2Ic\xb0Jh\xb3Db\xaf1M\x96\x0e\x17O\x00\x00\r\r\x00%/>\x8a0J\x9f.H\x9f-H\x9e.K\x9f-J\x9c&?\x91\x12"c\x03\x00!\x0b\x00#,;\x87+B\x9a(B\x99)D\x9a+E\x9a*D\x99";\x8d\x10\x1ea\x08\x00\x1d!,n\x1d/|\x19+z\x1c.\x7f%=\x94\x00\x00\x03\x07\x00\x19\x04\x00\x1b\x08\x030\x1a/}!9\x8a\x04\x00\x19\x13"n\x0e\x1c_\x01\x00\x11\t\x10F\x03\x00\x1f\x00\x00\x01\x8b\x8fZv\x00\x00\x00\x01tRNS\x00@\xe6\xd8f\x00\x00\x00\x01bKGD\x00\x88\x05\x1dH\x00\x00\x00\tpHYs\x00\x00\x0b\x12\x00\x00\x0b\x12\x01\xd2\xdd~\xfc\x00\x00\x00\x07tIME\x07\xd4\x01\x02\x137\r"\xc4\xd4\x02\x00\x00\x00\xf4IDATx\x9cc`\x80\x02F\x06\xec\x80\x99\t\xbb8\x1b+\x0bVq.N\x0evl\xe2"\xc2B\x82\x02\xfc\xfc||\xbc\xbc\xfc@\x0c\x04<\xdc q\x05y9Y\x19i))Iq1\t0!.&\xca\x03\x14\xd7\xd6\xd2\xd4\x90UWSVQQQ\x05\x13**\xcaJ\x8a\x0c\x0c\x16\xe6f\xfa\xa6\xa6\xa6&\xc6F\x06\xfa\x86`\xc2@_ER\x87\xc1\xc9\xd1\xc1\xde\xc6\x0e\x08l\x80\xc0\x16\x0cl\x8cT$\xf9\x19\x18|\xbc\xbd<= \xc0\xcd\x1d\x0cl\x0cT$\xf9\x80v\xf8\x84\x86\x04\x07\x05\x06\xc0\x81\x9b\x8d\xbe\x8a8/\x03D& :*\x12\n\x02\xdcm\rU\xc5\xc0\x12@\x99\xc4\x84\xf88\x08\x88\x04\xea0P\x91\x80H0\xf8\xa4$\'AX`\t\x98Q@\x90\x9e\x96\n\x95\x88\xf0w\xb5\xd6S\x16\x83I08e@\xe8\xd8p?\x17+]%\xb0\xcfQ\xe2#&\xcc\xd7\xd9RG\x91\x87\x1b\x00\xe4\xb53\x1e\x96h\xda\xbf\x00\x00\x00\x00IEND\xaeB`\x82'
134
135 album_template = u'''<?xml version="1.0" encoding="%(charenc)s"?>
136 <!DOCTYPE html
137      PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
138      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
139 <html xmlns="http://www.w3.org/1999/xhtml">
140 <head>
141 <meta http-equiv="Content-Type" content="text/html; charset=%(charenc)s" />
142 <link href="woolly.css" type="text/css" rel="stylesheet" />
143 %(uplink)s
144 <title>%(title)s</title>
145 </head>
146 <body>
147 <table cellpadding="3" width="85%%" align="center" cellspacing="0">
148 <tr><td class="header">%(paths)s</td></tr>
149 <tr>
150 <td>
151 <h1 class="title">%(title)s</h1>
152 %(description)s
153 </td>
154 </tr>
155 <tr>
156 <td align="center">
157 <table cellpadding="20">
158 %(subalbumentries)s
159 %(imageentries)s
160 </table>
161 </td>
162 </tr>
163 <tr>
164 <td class="footer" colspan="3">
165 <p>&nbsp;</p>
166 <hr/>
167 <small>%(blurb)s</small>
168 </td>
169 </tr>
170 </table>
171 </body>
172 </html>
173 '''
174
175 thumbnails_frame_template = u'''<?xml version="1.0" encoding="%(charenc)s"?>
176 <!DOCTYPE html
177      PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
178      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
179 <html xmlns="http://www.w3.org/1999/xhtml">
180 <head>
181 <meta http-equiv="Content-Type" content="text/html; charset=%(charenc)s" />
182 <link rel="stylesheet" href="../woolly.css" type="text/css" />
183 </head>
184 <body>
185 %(entries)s
186 </body>
187 </html>
188 '''
189
190 # At least Opera 6.12 behaves strangely with "text-align: center;" in
191 # stylesheet, so use align="center" instead.
192 thumbnails_frame_entry_template = u'''<a name="%(number)s"></a>
193 <a href="%(htmlref)s" class="toc" target="main">
194 <div align="center">
195 <img src="%(thumbimgref)s" class="toc" alt="" /></div>
196 </a>
197 '''
198
199 subalbum_entry_template = u'''<td align="center" valign="top">
200 <p>%(title)s</p>
201 <table border="0" cellspacing="0" cellpadding="0">
202 <tr>
203 <td><img src="%(iconsdir)s/frame-topleft.png" alt="" /></td>
204 <td><img src="%(iconsdir)s/frame-top.png" width="6" height="6" alt="" /></td>
205 <td><img src="%(iconsdir)s/frame-top.png" width="%(thumbwidth_minus_6)s" height="6" alt="" /></td>
206 <td><img src="%(iconsdir)s/frame-toprightupper.png" alt="" /></td>
207 </tr>
208 <tr>
209 <td rowspan="2"><img src="%(iconsdir)s/frame-left.png" width="6" height="%(thumbheight)s" alt="" /></td>
210 <td rowspan="2" colspan="2"><a href="%(htmlref)s"><img src="%(thumbimgref)s" width="%(thumbwidth)s" height="%(thumbheight)s" alt="Thumbnail"/></a></td>
211 <td><img src="%(iconsdir)s/frame-toprightlower.png" alt="" /></td>
212 </tr>
213 <tr><td><img src="%(iconsdir)s/frame-right.png" width="17" height="%(thumbheight_minus_6)s" alt="" /></td></tr>
214 <tr>
215 <td colspan="2"><img src="%(iconsdir)s/frame-bottomleft.png" alt="" /></td>
216 <td><img src="%(iconsdir)s/frame-bottom.png" width="%(thumbwidth_minus_6)s" height="17" alt="" /></td>
217 <td><img src="%(iconsdir)s/frame-bottomright.png" alt="" /></td>
218 </tr>
219 </table>
220 </td>
221 '''
222
223 image_entry_template = u'''<td align="left" valign="bottom">
224 <a href="%(frameref)s">
225 <img class="thinborder" src="%(thumbimgref)s" alt="" />
226 </a>
227 </td>
228 '''
229
230 image_frameset_template = u'''<?xml version="1.0" encoding="%(charenc)s"?>
231 <!DOCTYPE html
232      PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN"
233      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
234 <html xmlns="http://www.w3.org/1999/xhtml">
235 <head>
236 <meta http-equiv="Content-Type" content="text/html; charset=%(charenc)s" />
237 <link rel="stylesheet" href="../woolly.css" type="text/css" />
238 %(uplink)s
239 <title>%(albumtitle)s</title>
240 </head>
241 <frameset cols="100%%, %(thumbnailsframewidth)s">
242 <frame name="main" src="%(imageframeref)s" />
243 <frame name="toc" src="%(thumbnailsframeref)s#%(imagenumber)s" marginheight="20" />
244 <noframes>
245 <body>
246 This album needs frames. Sorry.
247 </body>
248 </noframes>
249 </frameset>
250 </html>
251 '''
252
253 image_frame_template = u'''<?xml version="1.0" encoding="%(charenc)s"?>
254 <!DOCTYPE html
255      PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
256      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
257 <html xmlns="http://www.w3.org/1999/xhtml">
258 <head>
259 <meta http-equiv="Content-Type" content="text/html; charset=%(charenc)s" />
260 <link href="../woolly.css" type="text/css" rel="stylesheet" />
261 <title>%(title)s</title>
262 </head>
263 <body onLoad="parent.toc.location.hash='%(thumbnailsanchor)s';">
264 <table cellpadding="3" width="85%%" align="center" cellspacing="0">
265 <tr><td class="header">%(paths)s</td></tr>
266 <tr>
267 <td>
268
269 <table width="100%%">
270 <tr>
271 <td align="left" colspan="3">
272 <table>
273 <tr>
274 <td>%(previous)s</td>
275 <td>%(next)s</td>
276 <td><img src="%(iconsdir)s/1x1.png" height="1" width="20" alt="" ></td>
277 <td>%(smaller)s</td>
278 <td>%(larger)s</td>
279 </tr>
280 </table>
281 </td>
282 </tr>
283 <tr>
284 <td width="50%%"></td>
285 <td align="center"><a %(nextlink)s><img class="thinborder" src="%(imgref)s" width="%(imgwidth)s" height="%(imgheight)s" align="center" alt="The image" /></a></td>
286 <td width="50%%"></td>
287 </tr>
288 <tr><td></td><td class="info">%(info)s</td><td></td></tr>
289 <tr>
290 <td class="footer" colspan="3">
291 <p>&nbsp;</p>
292 %(cache_previous_image)s
293 %(cache_next_image)s
294 <hr/>
295 <small>%(blurb)s Image ID: %(imgid)s.</small>
296 </td>
297 </tr>
298 </table>
299
300 </td>
301 </tr>
302 </table>
303 </body>
304 </html>
305 '''
306
307
308 class OutputGenerator(OutputEngine):
309     '''A class implementing the "woolly" output module.'''
310
311     def __init__(self, env, character_encoding):
312         OutputEngine.__init__(self, env)
313         self.iconsdir = None
314         self.env = env
315         self.charEnc = character_encoding
316         if env.config.has_option("woolly", "display_categories"):
317             displayCategories = re.split(r"(?:,|\s)\s*", env.config.get(
318                 "woolly",
319                 "display_categories"))
320             self.displayCategories = [
321                 env.shelf.getCategoryByTag(x)
322                 for x in displayCategories
323                 if x]
324         else:
325             self.displayCategories = []
326         self.autoImageDescTemplate = ""
327         try:
328             if env.config.getboolean("woolly", "enable_auto_descriptions"):
329                 tmpl = env.config.get("woolly", "auto_descriptions_template")
330                 self.autoImageDescTags = re.findall("<(.*?)>", tmpl)
331                 self.autoImageDescTemplate = re.sub(
332                     "<(.*?)>", r"%(\1)s", tmpl)
333         except ValueError:
334             pass
335
336
337     def preGeneration(self, unused):
338         """Method called before generation of the output."""
339         self.iconsdir = "@icons"
340         self.makeDirectory(self.iconsdir)
341
342
343     def postGeneration(self, root):
344         """Method called after generation of the output."""
345         if self.env.verbose:
346             self.env.out("Generating index page, style sheet and icons...\n")
347         self.symlinkFile(
348             "%s.html" % root.getTag(),
349             "index.html")
350         self.writeFile("woolly.css", css, self.charEnc)
351         for data, filename in [
352                 (transparent_1x1_png, "1x1.png"),
353                 (previous_png, "previous.png"),
354                 (noprevious_png, "noprevious.png"),
355                 (next_png, "next.png"),
356                 (nonext_png, "nonext.png"),
357                 (smaller_png, "smaller.png"),
358                 (nosmaller_png, "nosmaller.png"),
359                 (larger_png, "larger.png"),
360                 (nolarger_png, "nolarger.png"),
361                 (nextalbum_png, "nextalbum.png"),
362                 (previousalbum_png, "previousalbum.png"),
363                 (frame_bottom_png, "frame-bottom.png"),
364                 (frame_bottomleft_png, "frame-bottomleft.png"),
365                 (frame_bottomright_png, "frame-bottomright.png"),
366                 (frame_left_png, "frame-left.png"),
367                 (frame_right_png, "frame-right.png"),
368                 (frame_top_png, "frame-top.png"),
369                 (frame_topleft_png, "frame-topleft.png"),
370                 (frame_toprightlower_png, "frame-toprightlower.png"),
371                 (frame_toprightupper_png, "frame-toprightupper.png")]:
372             self.writeFile(
373                 os.path.join(self.iconsdir, filename), data, binary=True)
374
375
376     def generateAlbum(self, album, subalbums, images, paths):
377         """Method called to generate output of an album.
378
379         Arguments:
380
381         album     -- The Album instance.
382         subalbums -- Album children of the album.
383         images    -- Image children of the album.
384         """
385         # ------------------------------------------------------------
386         # Create album overview pages, one per size limit.
387         # ------------------------------------------------------------
388
389         self.makeDirectory(str(album.getId()))
390         for wlim, hlim in self.env.imagesizelimits:
391             pathtext = self._generatePathText(wlim, hlim, paths, "")
392
393             # Create uplink.
394             if len(paths[0]) > 1:
395                 uplink = '<link rel="up" href="%s-%dx%d.html" />' % (
396                     paths[0][-2].getTag(),
397                     wlim,
398                     hlim)
399             else:
400                 uplink = ""
401
402             # Create text for subalbum entries.
403             if subalbums:
404                 number = 0
405                 subalbumtextElements = ["<tr>\n"]
406                 for subalbum in subalbums:
407                     if number != 0 and number % 3 == 0:
408                         subalbumtextElements.append("</tr>\n<tr>\n")
409
410                     frontimage = self._getFrontImage(subalbum)
411                     if frontimage:
412                         thumbimgref, thumbwidth, thumbheight = \
413                             self.getImageReference(
414                                 frontimage,
415                                 self.env.thumbnailsizelimit[0],
416                                 self.env.thumbnailsizelimit[1])
417                     else:
418                         thumbimgref = "%s/%s" % (self.iconsdir, "1x1.png")
419                         thumbwidth = self.env.thumbnailsizelimit[0]
420                         thumbheight = 3 * self.env.thumbnailsizelimit[0] // 4
421
422                     title = (subalbum.getAttribute(u"title") or
423                              subalbum.getTag())
424                     subalbumtextElements.append(subalbum_entry_template % {
425                         "iconsdir": self.iconsdir,
426                         "htmlref": "%s-%dx%d.html" % (
427                             subalbum.getTag(),
428                             wlim,
429                             hlim),
430                         "thumbheight": thumbheight,
431                         "thumbheight_minus_6": thumbheight - 6,
432                         "thumbimgref": thumbimgref,
433                         "thumbwidth": thumbwidth,
434                         "thumbwidth_minus_6": thumbwidth - 6,
435                         "title": title,
436                         })
437                     number += 1
438                 subalbumtextElements.append("</tr>\n")
439                 subalbumtext = "".join(subalbumtextElements)
440             else:
441                 subalbumtext = ""
442
443             # Create text for image entries.
444             if images:
445                 number = 0
446                 imagetextElements = ["<tr>\n"]
447                 for image in images:
448                     if number != 0 and number % 3 == 0:
449                         imagetextElements.append("</tr>\n<tr>\n")
450                     imagetextElements.append(image_entry_template % {
451                         "frameref": "%s/%s-%dx%d-frame.html" % (
452                             album.getId(),
453                             number,
454                             wlim,
455                             hlim),
456                         "thumbimgref": self.getImageReference(
457                             image,
458                             self.env.thumbnailsizelimit[0],
459                             self.env.thumbnailsizelimit[1])[0],
460                         })
461                     number += 1
462                 imagetextElements.append("</tr>\n")
463                 imagetext = "".join(imagetextElements)
464             else:
465                 imagetext = ""
466
467             # Album overview.
468             desc = album.getAttribute(u"description") or u""
469             title = album.getAttribute(u"title") or album.getTag()
470
471             filename = "%s-%dx%d.html" % (album.getTag(), wlim, hlim)
472             self.writeFile(
473                 filename,
474                 album_template % {
475                     "blurb": self.blurb,
476                     "charenc": self.charEnc,
477                     "description": desc,
478                     "imageentries": imagetext,
479                     "paths": pathtext,
480                     "subalbumentries": subalbumtext,
481                     "thishref": filename,
482                     "title": title,
483                     "uplink": uplink,
484                 },
485                 self.charEnc)
486             self._maybeMakeUTF8Symlink("%s-%dx%d.html" % (album.getTag(),
487                                                           wlim,
488                                                           hlim))
489
490         # ------------------------------------------------------------
491         # Create image thumbnails frame, one per size limit.
492         # ------------------------------------------------------------
493
494         for wlim, hlim in self.env.imagesizelimits:
495             # Create text for image thumbnails frame.
496             thumbnailsframeElements = []
497             number = 0
498             for image in images:
499                 thumbnailsframeElements.append(
500                     thumbnails_frame_entry_template % {
501                         "htmlref": "%s-%dx%d.html" % (
502                             number,
503                             wlim,
504                             hlim),
505                         "number": number,
506                         "thumbimgref": "../" + self.getImageReference(
507                             image,
508                             self.env.thumbnailsizelimit[0],
509                             self.env.thumbnailsizelimit[1])[0],
510                         })
511                 number += 1
512             thumbnailstext = "\n".join(thumbnailsframeElements)
513
514             # Image thumbnails frame.
515             self.writeFile(
516                 os.path.join(str(album.getId()),
517                              "thumbnails-%dx%d.html" % (wlim, hlim)),
518                 thumbnails_frame_template % {
519                     "charenc": self.charEnc,
520                     "entries": thumbnailstext},
521                 self.charEnc)
522             self._maybeMakeUTF8Symlink(
523                 os.path.join(str(album.getId()),
524                              "thumbnails-%dx%d.html" % (wlim, hlim)))
525
526         # ------------------------------------------------------------
527         # Create album symlink to default size limit.
528         # ------------------------------------------------------------
529
530         self.symlinkFile(
531             "%s-%dx%d.html" % (album.getTag(),
532                                self.env.defaultsizelimit[0],
533                                self.env.defaultsizelimit[1]),
534             "%s.html" % album.getTag())
535         self._maybeMakeUTF8Symlink("%s.html" % album.getTag())
536
537
538     def generateImage(self, album, image, images, number, paths):
539         """Method called to generate output of an album.
540
541         Arguments:
542
543         album     -- The parent album of the image.
544         image     -- The Image instance.
545         images    -- A list of images in the album.
546         number    -- The current image's index in the image list.
547         paths     -- A list of lists of Album instances.
548         """
549         # ------------------------------------------------------------
550         # Create image frameset and image fram, one per size limit.
551         # ------------------------------------------------------------
552
553         for limnumber, (wlim, hlim) in enumerate(self.env.imagesizelimits):
554             pathtext = self._generatePathText(wlim, hlim, paths, "../")
555             uplink = \
556                 '<link rel="up" href="../%s-%dx%d.html" target="_top" />' % (
557                     paths[0][-1].getTag(),
558                     wlim,
559                     hlim)
560
561             if number > 0:
562                 previoustext = (
563                     '<a href="%s"><img class="icon" src="../%s/previous.png"'
564                     ' alt="Previous image" /></a>' % (
565                         "%s-%dx%d.html" % (number - 1, wlim, hlim),
566                         self.iconsdir))
567                 cpi_text = (
568                     '<img src="%s" width="1" height="1" style="display: none"'
569                     ' alt="" >' % (
570                         "../" + self.getImageReference(
571                             images[number - 1], wlim, hlim)[0]))
572             else:
573                 previoustext = (
574                     '<img class="icon" src="../%s/noprevious.png"'
575                     ' alt="No previous image" />' % self.iconsdir)
576                 cpi_text = ""
577
578             if number < len(images) - 1:
579                 nextlink = 'href="%s-%dx%d.html"' % (number + 1, wlim, hlim)
580                 nexttext = (
581                     '<a href="%s"><img class="icon" src="../%s/next.png"'
582                     ' alt="Next image" /></a>' % (
583                         "%s-%dx%d.html" % (number + 1, wlim, hlim),
584                         self.iconsdir))
585                 cni_text = (
586                     '<img src="%s" width="1" height="1" style="display: none"'
587                     ' alt="" >' % (
588                         "../" + self.getImageReference(
589                             images[number + 1], wlim, hlim)[0]))
590             else:
591                 nextlink = ""
592                 nexttext = (
593                     '<img class="icon" src="../%s/nonext.png"'
594                     ' alt="No next image" />' % self.iconsdir)
595                 cni_text = ""
596
597             if limnumber > 0:
598                 smallertext = (
599                     '<a href="%s" target="_top"><img class="icon"'
600                     ' src="../%s/smaller.png" alt="Smaller image" /></a>' % (
601                         "%s-%dx%d-frame.html" % (
602                             number,
603                             self.env.imagesizelimits[limnumber - 1][0],
604                             self.env.imagesizelimits[limnumber - 1][1]),
605                         self.iconsdir))
606
607             else:
608                 smallertext = (
609                     '<img class="icon" src="../%s/nosmaller.png"'
610                     ' alt="No smaller image available" />' % self.iconsdir)
611
612             if limnumber < len(self.env.imagesizelimits) - 1:
613                 largertext = (
614                     '<a href="%s" target="_top"><img class="icon"'
615                     ' src="../%s/larger.png" alt="Larger image" /></a>' % (
616                         "%s-%dx%d-frame.html" % (
617                             number,
618                             self.env.imagesizelimits[limnumber + 1][0],
619                             self.env.imagesizelimits[limnumber + 1][1]),
620                         self.iconsdir))
621             else:
622                 largertext = (
623                     '<img class="icon" src="../%s/nolarger.png"'
624                     ' alt="No larger image available" />' % self.iconsdir)
625
626             desc = (
627                 image.getAttribute(u"description") or
628                 image.getAttribute(u"title") or
629                 u"")
630             title = image.getAttribute(u"title") or u""
631
632             imageCategories = list(image.getCategories())
633             infotextElements = []
634             if desc:
635                 descElement = desc
636             else:
637                 if self.autoImageDescTemplate:
638                     catdict = {}
639                     for tag in self.autoImageDescTags:
640                         catlist = []
641                         cat = self.env.shelf.getCategoryByTag(tag)
642                         for imgcat in imageCategories:
643                             if cat.isParentOf(imgcat, True):
644                                 catlist.append(imgcat)
645                         catdict[tag] = ", ".join(
646                             [x.getDescription() for x in catlist])
647                     descElement = self.autoImageDescTemplate % catdict
648                 else:
649                     descElement = ""
650             infotextElements.append("<p>%s</p>\n" % descElement)
651             infotextElements.append(
652                 '<table border="0" cellpadding="0" cellspacing="0"'
653                 ' width="100%">\n<tr>')
654             firstrow = True
655             for dispcat in self.displayCategories:
656                 matching = [x.getDescription()
657                             for x in imageCategories
658                             if dispcat.isParentOf(x, True)]
659                 if matching:
660                     matching.sort()
661                     if firstrow:
662                         firstrow = False
663                     else:
664                         infotextElements.append("<td></td></tr>\n<tr>")
665                     infotextElements.append(
666                         '<td align="left"><small><b>%s</b>:'
667                         ' %s</small></td>' % (
668                             dispcat.getDescription(),
669                             ", ".join(matching)))
670             infotextElements.append('</td><td align="right">')
671             timestamp = image.getAttribute(u"captured")
672             if timestamp:
673                 infotextElements.append(
674                     "<small>%s</small><br />" % timestamp)
675             infotextElements.append("</td></tr></table>")
676             infotext = "".join(infotextElements)
677
678             self.writeFile(
679                 os.path.join(
680                     str(album.getId()),
681                     "%s-%dx%d-frame.html" % (number, wlim, hlim)),
682                 image_frameset_template % {
683                     "albumtitle": title,
684                     "charenc": self.charEnc,
685                     "imageframeref": "%s-%dx%d.html" % (
686                         number,
687                         wlim,
688                         hlim),
689                     "imagenumber": number,
690                     "thumbnailsframeref": "thumbnails-%dx%d.html" % (
691                         wlim, hlim),
692                     "thumbnailsframewidth": \
693                         self.env.thumbnailsizelimit[0] + 70,
694                     "uplink": uplink,
695                     },
696                 self.charEnc)
697
698             imgref, imgwidth, imgheight = self.getImageReference(
699                 image, wlim, hlim)
700             self.writeFile(
701                 os.path.join(str(album.getId()),
702                              "%s-%dx%d.html" % (number, wlim, hlim)),
703                 image_frame_template % {
704                     "blurb": self.blurb,
705                     "cache_previous_image": cpi_text,
706                     "cache_next_image": cni_text,
707                     "charenc": self.charEnc,
708                     "iconsdir": "../" + self.iconsdir,
709                     "imgheight": imgheight,
710                     "imgid": image.getId(),
711                     "imgmaxwidth": wlim,
712                     "imgref": "../" + imgref,
713                     "imgwidth": imgwidth,
714                     "info": infotext,
715                     "larger": largertext,
716                     "next": nexttext,
717                     "nextlink": nextlink,
718                     "paths": pathtext,
719                     "previous": previoustext,
720                     "smaller": smallertext,
721                     "thumbnailsanchor": str(number),
722                     "title": title,
723                     },
724                 self.charEnc)
725
726
727     def _generatePathText(self, wlim, hlim, paths, pathprefix):
728         """Internal helper method."""
729
730         # Create path text, used in top of the album overview.
731         pathtextElements = []
732         pathtextElements.append("<table width=\"100%\">\n")
733         for path in paths:
734             pathtextElements.append("<tr><td style=\"width: 60%\">\n")
735             els = []
736             for node in path:
737                 title = node.getAttribute(u"title") or node.getTag()
738                 els.append(
739                     '''<a href="%(pathprefix)s%(htmlref)s" target="_top">'''
740                     '''%(title)s</a>''' % {
741                         "htmlref": "%s-%dx%d.html" % (
742                             node.getTag(),
743                             wlim,
744                             hlim),
745                         "pathprefix": pathprefix,
746                         "title": title,
747                         })
748             pathtextElements.append(u" \xbb ".join(els))
749             pathtextElements.append("</td>\n")
750             pathtextElements.append(
751                 "<td width=\"40%\" align=\"right\" style=\"text-align:"
752                 " right; width: 40%\">\n")
753             if len(path) == 1:
754                 prevalbumtext = ""
755             else:
756                 parent = path[-2]
757                 this = path[-1]
758                 children = list(parent.getAlbumChildren())
759                 thispos = children.index(this)
760                 if thispos == 0:
761                     # No previous sibling.
762                     prevalbumtext = ""
763                 else:
764                     sibling = children[thispos - 1]
765                     title = (sibling.getAttribute(u"title") or
766                              sibling.getTag())
767                     prevalbumtext = (
768                         '<a href="%(pathprefix)s%(htmlref)s" target="_top">'
769                         '<img class="icon" src="%(pathprefix)s%(iconsdir)s/'
770                         'previousalbum.png" alt="Previous album" /></a>'
771                         '&nbsp;<a href="%(pathprefix)s%(htmlref)s"'
772                         ' target="_top">%(title)s</a>' % {
773                             "htmlref": "%s-%dx%d.html" % (
774                                 sibling.getTag(),
775                                 wlim,
776                                 hlim),
777                             "iconsdir": self.iconsdir,
778                             "pathprefix": pathprefix,
779                             "title": title.replace(" ", "&nbsp;")
780                             })
781                 if thispos == len(children) - 1:
782                     # No next sibling.
783                     nextalbumtext = ""
784                 else:
785                     sibling = children[thispos + 1]
786                     title = (sibling.getAttribute(u"title") or
787                              sibling.getTag())
788                     nextalbumtext = (
789                         '<a href="%(pathprefix)s%(htmlref)s" target="_top">'
790                         '<img class="icon" src="%(pathprefix)s%(iconsdir)s/'
791                         'nextalbum.png" alt="Next album" /></a>'
792                         '&nbsp;<a href="%(pathprefix)s%(htmlref)s"'
793                         ' target="_top">%(title)s</a>' % {
794                             "htmlref": "%s-%dx%d.html" % (
795                                 sibling.getTag(),
796                                 wlim,
797                                 hlim),
798                             "iconsdir": self.iconsdir,
799                             "pathprefix": pathprefix,
800                             "title": title.replace(" ", "&nbsp;"),
801                             })
802                 pathtextElements.append(prevalbumtext)
803                 pathtextElements.append("\n")
804                 pathtextElements.append(nextalbumtext)
805             pathtextElements.append("</td></tr>\n")
806         pathtextElements.append("</table>\n")
807         return "".join(pathtextElements)
808
809
810     def _getFrontImage(self, obj, visited=None):
811         """Internal helper method."""
812         if visited and obj.getId() in visited:
813             return None
814
815         if obj.isAlbum():
816             if not visited:
817                 visited = []
818             visited.append(obj.getId())
819             thumbid = obj.getAttribute(u"frontimage")
820             if thumbid:
821                 from kofoto.shelf import ImageDoesNotExistError
822                 try:
823                     return self.env.shelf.getImage(int(thumbid))
824                 except ImageDoesNotExistError:
825                     pass
826             children = iter(obj.getChildren())
827             try:
828                 return self._getFrontImage(children.next(), visited)
829             except StopIteration:
830                 return None
831         else:
832             return obj
833
834
835     def _maybeMakeUTF8Symlink(self, filename):
836         """Internal helper method."""
837         try:
838             # Check whether the filename contains ASCII characters
839             # only. If so, do nothing.
840             filename.encode("ascii")
841         except UnicodeError:
842             if filename.encode(self.charEnc) != filename.encode("utf-8"):
843                 self.symlinkFile(
844                     os.path.basename(filename.encode(self.charEnc)),
845                     filename.encode("utf-8"))