added rendertest/ directory
[swftools.git] / rendertest / testpdfs.py
1 import Image
2 import ImageChops
3 import ImageFilter
4 import sys
5 import os
6 import traceback
7 from athana import getTALstr
8 import random
9 import md5
10
11 filenames = []
12 directories = ["pdfs"]
13
14 SWFRENDER="swfrender"
15 PDFTOPPM="pdftoppm"
16 CONVERT="convert"
17 PDF2SWF="pdf2swf"
18
19 #COMPARE=["xpdf", PDF2SWF+" -s poly2bitmap"]
20 #OUTPUTDIR = "results.poly2bitmap/"
21
22 COMPARE=["xpdf", PDF2SWF+" -s convertgradients"]
23 OUTPUTDIR = "results.pdf2swf/"
24
25 #COMPARE=[PDF2SWF, PDF2SWF+" --flatten"]
26 #OUTPUTDIR = "results.flatten/"
27
28 counter = 1
29
30 def randstr():
31     return md5.md5(str(random.random())).hexdigest()[0:8]
32
33 def unlink(file):
34     try:
35         os.unlink(file)
36     except:
37         pass
38
39 def system(command):
40     if ">" not in command:
41         if os.system(command + " > /tmp/log.txt 2>&1") & 0xff00:
42             error = open("/tmp/log.txt", "rb").read()
43             print error
44             return error
45     else:
46         if os.system(command) & 0xff00:
47             return "Unknown error in "+command
48
49 class ConversionError:
50     def __init__(self, msg):
51         self.msg = msg
52     def __str__(self):
53         return self.msg
54
55 class TooComplexError:
56     def __init__(self, msg):
57         self.msg = msg
58     def __str__(self):
59         return self.msg
60
61 class BadMatch:
62     def __init__(self, msg):
63         self.msg = msg
64     def __str__(self):
65         return self.msg
66
67 def formatException():
68     s = "Exception "+str(sys.exc_info()[0])
69     info = sys.exc_info()[1]
70     if info:
71         s += " "+str(info)
72     s += "\n"
73     for l in traceback.extract_tb(sys.exc_info()[2]):
74         s += "  File \"%s\", line %d, in %s\n" % (l[0],l[1],l[2])
75         s += "    %s\n" % l[3]
76     return s
77
78
79 class PDFPage:
80     def __init__(self, filename, page, width, height):
81         self.filename = filename
82         self.page = page
83         self.rating = None
84         self.message = None
85         self.htmlmessage = None
86         self.file1 = None
87         self.file2 = None
88         self.file12 = None
89         self.html12 = None
90         self.htmldiff = None
91         self.width,self.height = width,height
92
93     def runtools(self, filename, page, file1, file2, file12):
94
95         badness = 0.0
96
97         if COMPARE[0] == "xpdf":
98             unlink("/tmp/test-%06d.ppm" % page)
99             error = system(PDFTOPPM + " -r 72 -f %d -l %d '%s' /tmp/test" % (page, page, filename))
100             if error and "supports 65536" in error:
101                 raise TooComplexError(error)
102             if error:
103                 raise ConversionError(error)
104             unlink(file2)
105             error = system(CONVERT + " /tmp/test-%06d.ppm  %s" % (page, file2))
106             if error:
107                 raise ConversionError(error)
108             unlink("/tmp/test-%06d.ppm" % page)
109         else:
110             unlink("/tmp/test.swf")
111             unlink("svp.ps")
112             error = system(COMPARE[0]+ " -Q 300 -p%d '%s' -o /tmp/test.swf" % (page, filename))
113             #system("mv svp.ps %s.ps" % randstr())
114             if error and "supports 65536" in error:
115                 raise TooComplexError(error)
116             if error:
117                 raise ConversionError(error)
118             unlink(file2)
119             error = system(SWFRENDER + " /tmp/test.swf -o %s" % file2)
120             if error:
121                 raise ConversionError(error)
122             unlink("/tmp/test.swf")
123         
124         unlink("/tmp/test.swf")
125         error = system(COMPARE[1]+ " -Q 300 -p%d '%s' -o /tmp/test.swf" % (page, filename))
126         if error:
127             raise ConversionError(error)
128         unlink(file1)
129         error = system(SWFRENDER + " /tmp/test.swf -o %s" % file1)
130         if error:
131             raise ConversionError(error)
132         unlink("/tmp/test.swf")
133
134         unlink(file12)
135         pic1 = Image.open(file1)
136         pic1.load()
137         self.width1 = pic1.size[0]
138         self.height1 = pic1.size[1]
139         
140         pic2 = Image.open(file2)
141         pic2.load()
142         self.width2 = pic2.size[0]
143         self.height2 = pic2.size[1]
144
145         if abs(self.width1-self.width2)>5 or abs(self.height1!=self.height2)>5:
146             badness += 65536*abs(self.width2-self.width1)*max(self.height1,self.height2)+65536*abs(self.height2-self.height1)*max(self.width1,self.width2)
147
148         minx = min(self.width1,self.width2)
149         miny = min(self.height1,self.height2)
150
151         pic1 = pic1.crop((0,0,minx,miny))
152         pic1 = pic1.convert("RGB")
153         pic1 = pic1.filter(ImageFilter.BLUR)
154         pic2 = pic2.crop((0,0,minx,miny))
155         pic2 = pic2.convert("RGB")
156         pic2 = pic2.filter(ImageFilter.BLUR)
157
158         diffimage = ImageChops.difference(pic1,pic2)
159         diffimage.save(file12, "PNG")
160         
161         # compute quadratical difference
162         diff = diffimage.histogram()
163         for i in range(1,128):
164             badness += (diff[i] + diff[256-i])*float(i*i)
165             badness += (diff[256+i] + diff[256+256-i])*float(i*i)
166             badness += (diff[512+i] + diff[512+256-i])*float(i*i)
167
168         badness /= (minx*miny)*3
169
170         return badness
171
172     def compare(self):
173         try:
174             global counter
175             self.file1 = str(counter) + ".png"
176             counter = counter + 1
177             self.file2 = str(counter) + ".png"
178             counter = counter + 1
179             self.file12 = str(counter) + ".png"
180             counter = counter + 1
181             self.rating = self.runtools(self.filename, self.page, OUTPUTDIR + self.file1, OUTPUTDIR + self.file2, OUTPUTDIR + self.file12)
182         except BadMatch:
183             self.rating = 65534.0
184             self.message = formatException()
185             print self.message
186         except ConversionError:
187             self.rating = 65535.0
188             self.message = formatException()
189             print self.message
190         except TooComplexError:
191             self.rating = 65536.0
192             self.message = formatException()
193             print self.message
194         except:
195             self.rating = 65537.0
196             self.message = formatException()
197             print self.message
198
199     def getsizes(self):
200         if self.message:
201             return ""
202         if abs(self.width1 - self.width2) > 5 or \
203            abs(self.height1 - self.height2) > 5:
204                return '<font color="red">%dx%d <-> %dx%d</font>' % (self.width1, self.height1, self.width2, self.height2)
205         else:
206                return '%dx%d,%dx%d' % (self.width1, self.height1, self.width2, self.height2)
207
208     def generatehtml(self):
209         global OUTPUTDIR
210         global counter
211         self.html12 = str(counter) + ".html"
212         counter = counter + 1
213         self.htmldiff = str(counter) + ".html"
214         counter = counter + 1
215         fi = open(OUTPUTDIR + self.html12, "wb")
216         fi.write(getTALstr("""
217 <html><head></head>
218 <body>
219 <tal:block tal:replace="python:'File: '+self.filename"/><br>
220 <tal:block tal:replace="python:'Page: '+str(self.page)"/><br>
221 <tal:block tal:replace="python:'Rating: '+str(self.rating)"/><br>
222 <pre tal:condition="python:self.message" tal:replace="python:'Message: '+str(self.message)"/><br>
223 <hr>
224 <table cellspacing="0" cellpadding="0">
225 <tr><td><img tal:attributes="src python:self.file1"/></td><td><img tal:attributes="src python:self.file2"/></td></tr>
226 <tr><td>pdf2swf Version</td><td>pdftoppm Version</td></tr>
227 </table>
228 <hr>
229 </body>
230 </html>""", {"self": self}))
231         fi.close()
232         
233         fi = open(OUTPUTDIR + self.htmldiff, "wb")
234         fi.write(getTALstr("""
235 <html><head></head>
236 <body>
237 <tal:block tal:replace="python:'File: '+self.filename"/><br>
238 <tal:block tal:replace="python:'Page: '+str(self.page)"/><br>
239 <tal:block tal:replace="python:'Rating: '+str(self.rating)"/><br>
240 <pre tal:condition="python:self.message" tal:replace="python:'Message: '+str(self.message)"/><br>
241 <hr>
242 <img tal:attributes="src python:self.file12"/>
243 <hr>
244 </body>
245 </html>""", {"self": self}))
246         fi.close()
247         
248         if self.message:
249             self.htmlmessage = str(counter) + ".html"
250             counter = counter + 1
251             fi = open(OUTPUTDIR + self.htmlmessage, "wb")
252             fi.write(getTALstr("""
253 <html><head></head>
254 <body>
255 <pre tal:content="raw python:self.message">
256 </pre>
257 </body>
258 </html>""", {"self": self}))
259             fi.close()
260         
261
262
263 def compare_pages(page1,page2):
264     if page1.rating < page2.rating:
265         return 1
266     elif page1.rating > page2.rating:
267         return -1
268     else:
269         return 0
270
271
272 def add_directory(directory):
273     if not os.path.isdir(directory):
274         print "bad directory:",directory
275         return
276     for file in os.listdir(directory):
277         global filenames
278         filename = os.path.join(directory, file)
279         if file.lower().endswith(".pdf"):
280             filenames += [filename]
281             print "+",filename
282         elif os.path.isdir(filename):
283             add_directory(filename)
284
285 pages = []
286 try:
287     os.mkdir(OUTPUTDIR)
288 except: pass
289
290 for file in filenames:
291     print "+",file
292
293 for dir in directories:
294     add_directory(dir)
295
296 for filename in filenames:
297     try:
298         unlink("/tmp/test.txt")
299         error = system(PDF2SWF + " -I %s -o /tmp/test.txt" % filename)
300         if error:
301             raise ConversionError(error)
302         fi = open("/tmp/test.txt", "rb")
303         for line in fi.readlines():
304             p = {}
305             for param in line.split(" "):
306                 key,value = param.split("=")
307                 p[key] = value
308             page = int(p["page"])
309             width = int(float(p["width"]))
310             height = int(float(p["height"]))
311             print filename, page, "%dx%d" % (width, height)
312             pdfpage = PDFPage(filename, page, width, height)
313             pdfpage.compare()
314
315             if width < 2000 and height < 2000:
316                 pages += [pdfpage]
317
318             # only consider the first 3 pages
319             if page > 3:
320                 break
321         fi.close()
322     except KeyboardInterrupt:
323         break
324     except:
325         pdfpage = PDFPage(filename, -1, -1, -1)
326         pdfpage.rating = 65536.0
327         pdfpage.message = formatException()
328         pages += [pdfpage]
329
330 pages.sort(compare_pages)
331
332 position = 1
333 for page in pages:
334     page.generatehtml()
335     page.position = position
336     position = position + 1
337
338 fi = open(OUTPUTDIR + "index.html", "wb")
339 fi.write(getTALstr("""<html>
340 <head></head>
341 <body>
342 <table border="1"><tr><th>Position</th><th>Rating</th><th>File</th><th>Size</th><th>Page</th><th>Images</th><th>Diff</th><th>Further Info</th></tr>
343 <tal:block tal:repeat="page pages">
344 <tr>
345 <td tal:content="python:page.position"/>
346 <td tal:content="python:page.rating"/>
347 <td tal:content="python:page.filename"/>
348 <td tal:content="raw python:page.getsizes()"/>
349 <td tal:content="python:page.page"/>
350 <td><a tal:attributes="href python:page.html12">Side by Side</a></td>
351 <td><a tal:attributes="href python:page.htmldiff">Difference</a></td>
352 <td><a tal:condition="python:page.message" tal:attributes="href python:page.htmlmessage">Error message</a></td>
353 </tr>
354 </tal:block>
355 </table>
356 </body>
357 </html>""", {"pages": pages}))
358 fi.close()
359