xref: /aosp_15_r20/external/skia/experimental/tools/pdf-comparison.py (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1*c8dee2aaSAndroid Build Coastguard Worker#!/usr/bin/env python
2*c8dee2aaSAndroid Build Coastguard Worker# Copyright 2019 Google LLC.
3*c8dee2aaSAndroid Build Coastguard Worker# Use of this source code is governed by a BSD-style license that can be
4*c8dee2aaSAndroid Build Coastguard Worker# found in the LICENSE file.
5*c8dee2aaSAndroid Build Coastguard Worker
6*c8dee2aaSAndroid Build Coastguard Worker'''
7*c8dee2aaSAndroid Build Coastguard WorkerThis tool compares the PDF output of Skia's DM tool of two commits.
8*c8dee2aaSAndroid Build Coastguard Worker
9*c8dee2aaSAndroid Build Coastguard WorkerIt relies on pdfium_test being in the PATH.  To build:
10*c8dee2aaSAndroid Build Coastguard Worker
11*c8dee2aaSAndroid Build Coastguard Workermkdir -p ~/src/pdfium
12*c8dee2aaSAndroid Build Coastguard Workercd ~/src/pdfium
13*c8dee2aaSAndroid Build Coastguard Workergclient config --unmanaged https://pdfium.googlesource.com/pdfium.git
14*c8dee2aaSAndroid Build Coastguard Workergclient sync
15*c8dee2aaSAndroid Build Coastguard Workercd pdfium
16*c8dee2aaSAndroid Build Coastguard Workergn gen out/default --args='pdf_enable_xfa=false pdf_enable_v8=false pdf_is_standalone=true'
17*c8dee2aaSAndroid Build Coastguard Workerninja -C out/default pdfium_test
18*c8dee2aaSAndroid Build Coastguard Workercp out/default/pdfium_test ~/bin/
19*c8dee2aaSAndroid Build Coastguard Worker'''
20*c8dee2aaSAndroid Build Coastguard Worker
21*c8dee2aaSAndroid Build Coastguard Workerimport os
22*c8dee2aaSAndroid Build Coastguard Workerimport re
23*c8dee2aaSAndroid Build Coastguard Workerimport shutil
24*c8dee2aaSAndroid Build Coastguard Workerimport subprocess
25*c8dee2aaSAndroid Build Coastguard Workerimport sys
26*c8dee2aaSAndroid Build Coastguard Workerimport tempfile
27*c8dee2aaSAndroid Build Coastguard Workerimport threading
28*c8dee2aaSAndroid Build Coastguard Worker
29*c8dee2aaSAndroid Build Coastguard WorkerEXTRA_GN_ARGS = os.environ.get('PDF_COMPARISON_GN_ARGS', '')
30*c8dee2aaSAndroid Build Coastguard Worker
31*c8dee2aaSAndroid Build Coastguard WorkerREFERENCE_BACKEND = 'gl' if 'PDF_COMPARISON_NOGPU' not in os.environ else '8888'
32*c8dee2aaSAndroid Build Coastguard Worker
33*c8dee2aaSAndroid Build Coastguard WorkerDPI = float(os.environ.get('PDF_COMPARISON_DPI', 72))
34*c8dee2aaSAndroid Build Coastguard Worker
35*c8dee2aaSAndroid Build Coastguard WorkerPDF_CONFIG = 'pdf' if 'PDF_COMPARISON_300DPI' not in os.environ else 'pdf300'
36*c8dee2aaSAndroid Build Coastguard Worker
37*c8dee2aaSAndroid Build Coastguard WorkerBAD_TESTS = [
38*c8dee2aaSAndroid Build Coastguard Worker  'image-cacherator-from-picture',
39*c8dee2aaSAndroid Build Coastguard Worker  'image-cacherator-from-raster',
40*c8dee2aaSAndroid Build Coastguard Worker  'mixershader',
41*c8dee2aaSAndroid Build Coastguard Worker  'shadermaskfilter_image',
42*c8dee2aaSAndroid Build Coastguard Worker  'tilemode_decal',
43*c8dee2aaSAndroid Build Coastguard Worker]
44*c8dee2aaSAndroid Build Coastguard Worker
45*c8dee2aaSAndroid Build Coastguard WorkerNINJA = 'ninja'
46*c8dee2aaSAndroid Build Coastguard Worker
47*c8dee2aaSAndroid Build Coastguard WorkerPDFIUM_TEST = 'pdfium_test'
48*c8dee2aaSAndroid Build Coastguard Worker
49*c8dee2aaSAndroid Build Coastguard WorkerNUM_THREADS = int(os.environ.get('PDF_COMPARISON_THREADS', 40))
50*c8dee2aaSAndroid Build Coastguard Worker
51*c8dee2aaSAndroid Build Coastguard WorkerSOURCES = ['gm']
52*c8dee2aaSAndroid Build Coastguard Worker
53*c8dee2aaSAndroid Build Coastguard Workerdef test_exe(cmd):
54*c8dee2aaSAndroid Build Coastguard Worker  with open(os.devnull, 'w') as o:
55*c8dee2aaSAndroid Build Coastguard Worker    try:
56*c8dee2aaSAndroid Build Coastguard Worker      subprocess.call([cmd], stdout=o, stderr=o)
57*c8dee2aaSAndroid Build Coastguard Worker    except OSError:
58*c8dee2aaSAndroid Build Coastguard Worker      return False
59*c8dee2aaSAndroid Build Coastguard Worker  return True
60*c8dee2aaSAndroid Build Coastguard Worker
61*c8dee2aaSAndroid Build Coastguard Workerdef print_cmd(cmd, o):
62*c8dee2aaSAndroid Build Coastguard Worker  m = re.compile('[^A-Za-z0-9_./-]')
63*c8dee2aaSAndroid Build Coastguard Worker  o.write('+ ')
64*c8dee2aaSAndroid Build Coastguard Worker  for c in cmd:
65*c8dee2aaSAndroid Build Coastguard Worker    if m.search(c) is not None:
66*c8dee2aaSAndroid Build Coastguard Worker      o.write(repr(c) + ' ')
67*c8dee2aaSAndroid Build Coastguard Worker    else:
68*c8dee2aaSAndroid Build Coastguard Worker      o.write(c + ' ')
69*c8dee2aaSAndroid Build Coastguard Worker  o.write('\n')
70*c8dee2aaSAndroid Build Coastguard Worker  o.flush()
71*c8dee2aaSAndroid Build Coastguard Worker
72*c8dee2aaSAndroid Build Coastguard Workerdef check_call(cmd, **kwargs):
73*c8dee2aaSAndroid Build Coastguard Worker  print_cmd(cmd, sys.stdout)
74*c8dee2aaSAndroid Build Coastguard Worker  return subprocess.check_call(cmd, **kwargs)
75*c8dee2aaSAndroid Build Coastguard Worker
76*c8dee2aaSAndroid Build Coastguard Workerdef check_output(cmd, **kwargs):
77*c8dee2aaSAndroid Build Coastguard Worker  print_cmd(cmd, sys.stdout)
78*c8dee2aaSAndroid Build Coastguard Worker  return subprocess.check_output(cmd, **kwargs)
79*c8dee2aaSAndroid Build Coastguard Worker
80*c8dee2aaSAndroid Build Coastguard Workerdef remove(*paths):
81*c8dee2aaSAndroid Build Coastguard Worker  for path in paths:
82*c8dee2aaSAndroid Build Coastguard Worker    os.remove(path)
83*c8dee2aaSAndroid Build Coastguard Worker
84*c8dee2aaSAndroid Build Coastguard Workerdef timeout(deadline, cmd):
85*c8dee2aaSAndroid Build Coastguard Worker  #print_cmd(cmd, sys.stdout)
86*c8dee2aaSAndroid Build Coastguard Worker  with open(os.devnull, 'w') as o:
87*c8dee2aaSAndroid Build Coastguard Worker    proc = subprocess.Popen(cmd, stdout=o, stderr=subprocess.STDOUT)
88*c8dee2aaSAndroid Build Coastguard Worker    timer = threading.Timer(deadline, proc.terminate)
89*c8dee2aaSAndroid Build Coastguard Worker    timer.start()
90*c8dee2aaSAndroid Build Coastguard Worker    proc.wait()
91*c8dee2aaSAndroid Build Coastguard Worker    timer.cancel()
92*c8dee2aaSAndroid Build Coastguard Worker    return proc.returncode
93*c8dee2aaSAndroid Build Coastguard Worker
94*c8dee2aaSAndroid Build Coastguard Workerdef is_same(path1, path2):
95*c8dee2aaSAndroid Build Coastguard Worker  if not os.path.isfile(path1) or not os.path.isfile(path2):
96*c8dee2aaSAndroid Build Coastguard Worker    return os.path.isfile(path1) == os.path.isfile(path2)
97*c8dee2aaSAndroid Build Coastguard Worker  with open(path1, 'rb') as f1:
98*c8dee2aaSAndroid Build Coastguard Worker    with open(path2, 'rb') as f2:
99*c8dee2aaSAndroid Build Coastguard Worker      while True:
100*c8dee2aaSAndroid Build Coastguard Worker        c1, c2 = f1.read(4096), f2.read(4096)
101*c8dee2aaSAndroid Build Coastguard Worker        if c1 != c2:
102*c8dee2aaSAndroid Build Coastguard Worker          return False
103*c8dee2aaSAndroid Build Coastguard Worker        if not c1:
104*c8dee2aaSAndroid Build Coastguard Worker          return True
105*c8dee2aaSAndroid Build Coastguard Worker
106*c8dee2aaSAndroid Build Coastguard Worker
107*c8dee2aaSAndroid Build Coastguard Workerdef getfilesoftype(directory, ending):
108*c8dee2aaSAndroid Build Coastguard Worker  for dirpath, _, filenames in os.walk(directory):
109*c8dee2aaSAndroid Build Coastguard Worker    rp = os.path.normpath(os.path.relpath(dirpath, directory))
110*c8dee2aaSAndroid Build Coastguard Worker    for f in filenames:
111*c8dee2aaSAndroid Build Coastguard Worker      if f.endswith(ending):
112*c8dee2aaSAndroid Build Coastguard Worker        yield os.path.join(rp, f)
113*c8dee2aaSAndroid Build Coastguard Worker
114*c8dee2aaSAndroid Build Coastguard Workerdef get_common_paths(dirs, ext):
115*c8dee2aaSAndroid Build Coastguard Worker  return sorted(list(
116*c8dee2aaSAndroid Build Coastguard Worker    set.intersection(*(set(getfilesoftype(d, ext)) for d in dirs))))
117*c8dee2aaSAndroid Build Coastguard Worker
118*c8dee2aaSAndroid Build Coastguard Workerdef printable_path(d):
119*c8dee2aaSAndroid Build Coastguard Worker  if 'TMPDIR' in os.environ:
120*c8dee2aaSAndroid Build Coastguard Worker    return d.replace(os.path.normpath(os.environ['TMPDIR']) + '/', '$TMPDIR/')
121*c8dee2aaSAndroid Build Coastguard Worker  return d
122*c8dee2aaSAndroid Build Coastguard Worker
123*c8dee2aaSAndroid Build Coastguard Workerdef spawn(cmd):
124*c8dee2aaSAndroid Build Coastguard Worker  with open(os.devnull, 'w') as o:
125*c8dee2aaSAndroid Build Coastguard Worker    subprocess.Popen(cmd, stdout=o, stderr=o)
126*c8dee2aaSAndroid Build Coastguard Worker
127*c8dee2aaSAndroid Build Coastguard Workerdef sysopen(arg):
128*c8dee2aaSAndroid Build Coastguard Worker  plat = sys.platform
129*c8dee2aaSAndroid Build Coastguard Worker  if plat.startswith('darwin'):
130*c8dee2aaSAndroid Build Coastguard Worker    spawn(["open", arg])
131*c8dee2aaSAndroid Build Coastguard Worker  elif plat.startswith('win'):
132*c8dee2aaSAndroid Build Coastguard Worker    # pylint: disable=no-member
133*c8dee2aaSAndroid Build Coastguard Worker    os.startfile(arg)
134*c8dee2aaSAndroid Build Coastguard Worker  else:
135*c8dee2aaSAndroid Build Coastguard Worker    spawn(["xdg-open", arg])
136*c8dee2aaSAndroid Build Coastguard Worker
137*c8dee2aaSAndroid Build Coastguard WorkerHTML_HEAD = '''
138*c8dee2aaSAndroid Build Coastguard Worker<!DOCTYPE html>
139*c8dee2aaSAndroid Build Coastguard Worker<html lang="en">
140*c8dee2aaSAndroid Build Coastguard Worker<head>
141*c8dee2aaSAndroid Build Coastguard Worker<meta charset="utf-8">
142*c8dee2aaSAndroid Build Coastguard Worker<title>DIFF</title>
143*c8dee2aaSAndroid Build Coastguard Worker<style>
144*c8dee2aaSAndroid Build Coastguard Workerbody{
145*c8dee2aaSAndroid Build Coastguard Workerbackground-size:16px 16px;
146*c8dee2aaSAndroid Build Coastguard Workerbackground-color:rgb(230,230,230);
147*c8dee2aaSAndroid Build Coastguard Workerbackground-image:
148*c8dee2aaSAndroid Build Coastguard Workerlinear-gradient(45deg,rgba(255,255,255,.2) 25%,transparent 25%,transparent 50%,
149*c8dee2aaSAndroid Build Coastguard Workerrgba(255,255,255,.2) 50%,rgba(255,255,255,.2) 75%,transparent 75%,transparent)}
150*c8dee2aaSAndroid Build Coastguard Workerdiv.r{position:relative;left:0;top:0}
151*c8dee2aaSAndroid Build Coastguard Workertable{table-layout:fixed;width:100%}
152*c8dee2aaSAndroid Build Coastguard Workerimg.s{max-width:100%;max-height:320;left:0;top:0}
153*c8dee2aaSAndroid Build Coastguard Workerimg.b{position:absolute;mix-blend-mode:difference}
154*c8dee2aaSAndroid Build Coastguard Worker</style>
155*c8dee2aaSAndroid Build Coastguard Worker<script>
156*c8dee2aaSAndroid Build Coastguard Workerfunction r(c,e,n,g){
157*c8dee2aaSAndroid Build Coastguard Workert=document.getElementById("t");
158*c8dee2aaSAndroid Build Coastguard Workerfunction ce(t){return document.createElement(t);}
159*c8dee2aaSAndroid Build Coastguard Workerfunction ct(n){return document.createTextNode(n);}
160*c8dee2aaSAndroid Build Coastguard Workerfunction ac(u,v){u.appendChild(v);}
161*c8dee2aaSAndroid Build Coastguard Workerfunction cn(u,v){u.className=v;}
162*c8dee2aaSAndroid Build Coastguard Workerfunction it(s){ td=ce("td"); a=ce("a"); a.href=s; img=ce("img"); img.src=s;
163*c8dee2aaSAndroid Build Coastguard Worker        cn(img,"s"); ac(a,img); ac(td,a); return td; }
164*c8dee2aaSAndroid Build Coastguard Workertr=ce("tr"); td=ce("td"); td.colSpan="4"; ac(td, ct(n)); ac(tr,td);
165*c8dee2aaSAndroid Build Coastguard Workerac(t,tr); tr=ce("tr"); td=ce("td"); dv=ce("div"); cn(dv,"r");
166*c8dee2aaSAndroid Build Coastguard Workerimg=ce("img"); img.src=c; cn(img,"s"); ac(dv,img); img=ce("img");
167*c8dee2aaSAndroid Build Coastguard Workerimg.src=e; cn(img,"s b"); ac(dv,img); ac(td,dv); ac(tr,td);
168*c8dee2aaSAndroid Build Coastguard Workerac(tr,it(c)); ac(tr,it(e)); ac(tr,it(g)); ac(t,tr); }
169*c8dee2aaSAndroid Build Coastguard Workerdocument.addEventListener('DOMContentLoaded',function(){
170*c8dee2aaSAndroid Build Coastguard Worker'''
171*c8dee2aaSAndroid Build Coastguard Worker
172*c8dee2aaSAndroid Build Coastguard WorkerHTML_TAIL = '''];
173*c8dee2aaSAndroid Build Coastguard Workerfor(i=0;i<z.length;i++){
174*c8dee2aaSAndroid Build Coastguard Workerr(c+z[i][0],e+z[i][0],z[i][2],c+z[i][1]);}},false);
175*c8dee2aaSAndroid Build Coastguard Worker</script></head><body><table id="t">
176*c8dee2aaSAndroid Build Coastguard Worker<tr><th>BEFORE-AFTER DIFF</th>
177*c8dee2aaSAndroid Build Coastguard Worker<th>BEFORE</th><th>AFTER</th>
178*c8dee2aaSAndroid Build Coastguard Worker<th>REFERENCE</th></tr>
179*c8dee2aaSAndroid Build Coastguard Worker</table></body></html>'''
180*c8dee2aaSAndroid Build Coastguard Worker
181*c8dee2aaSAndroid Build Coastguard Workerdef shard(fn, arglist):
182*c8dee2aaSAndroid Build Coastguard Worker  jobs = [[arg for j, arg in enumerate(arglist) if j % NUM_THREADS == i]
183*c8dee2aaSAndroid Build Coastguard Worker          for i in range(NUM_THREADS)]
184*c8dee2aaSAndroid Build Coastguard Worker  results = []
185*c8dee2aaSAndroid Build Coastguard Worker  def do_shard(*args):
186*c8dee2aaSAndroid Build Coastguard Worker    for arg in args:
187*c8dee2aaSAndroid Build Coastguard Worker      results.append(fn(arg))
188*c8dee2aaSAndroid Build Coastguard Worker  thread_list = []
189*c8dee2aaSAndroid Build Coastguard Worker  for job in jobs:
190*c8dee2aaSAndroid Build Coastguard Worker    t = threading.Thread(target=do_shard, args=job)
191*c8dee2aaSAndroid Build Coastguard Worker    t.start()
192*c8dee2aaSAndroid Build Coastguard Worker    thread_list += [t]
193*c8dee2aaSAndroid Build Coastguard Worker  for t in thread_list:
194*c8dee2aaSAndroid Build Coastguard Worker    t.join()
195*c8dee2aaSAndroid Build Coastguard Worker  return results
196*c8dee2aaSAndroid Build Coastguard Worker
197*c8dee2aaSAndroid Build Coastguard Workerdef shardsum(fn, arglist):
198*c8dee2aaSAndroid Build Coastguard Worker  'return the number of True results returned by fn(arg) for arg in arglist.'
199*c8dee2aaSAndroid Build Coastguard Worker  return sum(1 for result in shard(fn, arglist) if result)
200*c8dee2aaSAndroid Build Coastguard Worker
201*c8dee2aaSAndroid Build Coastguard Workerdef checkout_worktree(checkoutable):
202*c8dee2aaSAndroid Build Coastguard Worker  directory = os.path.join(tempfile.gettempdir(), 'skpdf_control_tree')
203*c8dee2aaSAndroid Build Coastguard Worker  commit = check_output(['git', 'rev-parse', checkoutable]).strip()
204*c8dee2aaSAndroid Build Coastguard Worker  if os.path.isdir(directory):
205*c8dee2aaSAndroid Build Coastguard Worker    try:
206*c8dee2aaSAndroid Build Coastguard Worker      check_call(['git', 'checkout', commit], cwd=directory)
207*c8dee2aaSAndroid Build Coastguard Worker      return directory
208*c8dee2aaSAndroid Build Coastguard Worker    except subprocess.CalledProcessError:
209*c8dee2aaSAndroid Build Coastguard Worker      shutil.rmtree(directory)
210*c8dee2aaSAndroid Build Coastguard Worker  check_call(['git', 'worktree', 'add', '-f', directory, commit])
211*c8dee2aaSAndroid Build Coastguard Worker  return directory
212*c8dee2aaSAndroid Build Coastguard Worker
213*c8dee2aaSAndroid Build Coastguard Workerdef build_skia(directory, executable):
214*c8dee2aaSAndroid Build Coastguard Worker  args = ('--args=is_debug=false'
215*c8dee2aaSAndroid Build Coastguard Worker          ' extra_cflags=["-DSK_PDF_LESS_COMPRESSION",'
216*c8dee2aaSAndroid Build Coastguard Worker          ' "-DSK_PDF_BASE85_BINARY"] ')
217*c8dee2aaSAndroid Build Coastguard Worker  if test_exe('ccache'):
218*c8dee2aaSAndroid Build Coastguard Worker    args += ' cc_wrapper="ccache"'
219*c8dee2aaSAndroid Build Coastguard Worker  args += EXTRA_GN_ARGS
220*c8dee2aaSAndroid Build Coastguard Worker  build_dir = directory + '/out/pdftest'
221*c8dee2aaSAndroid Build Coastguard Worker  check_call([sys.executable, 'bin/sync'], cwd=directory)
222*c8dee2aaSAndroid Build Coastguard Worker  check_call([directory + '/bin/gn', 'gen', 'out/pdftest', args],
223*c8dee2aaSAndroid Build Coastguard Worker             cwd=directory)
224*c8dee2aaSAndroid Build Coastguard Worker  check_call([NINJA, executable], cwd=build_dir)
225*c8dee2aaSAndroid Build Coastguard Worker  return os.path.join(build_dir, executable)
226*c8dee2aaSAndroid Build Coastguard Worker
227*c8dee2aaSAndroid Build Coastguard Workerdef build_and_run_dm(directory, data_dir):
228*c8dee2aaSAndroid Build Coastguard Worker  dm = build_skia(directory, 'dm')
229*c8dee2aaSAndroid Build Coastguard Worker  for source in SOURCES:
230*c8dee2aaSAndroid Build Coastguard Worker    os.makedirs(os.path.join(data_dir, PDF_CONFIG, source))
231*c8dee2aaSAndroid Build Coastguard Worker  dm_args = [dm, '--src'] + SOURCES + ['--config', PDF_CONFIG, '-w', data_dir]
232*c8dee2aaSAndroid Build Coastguard Worker  if BAD_TESTS:
233*c8dee2aaSAndroid Build Coastguard Worker    dm_args += ['-m'] + ['~^%s$' % x for x in BAD_TESTS]
234*c8dee2aaSAndroid Build Coastguard Worker  check_call(dm_args, cwd=directory)
235*c8dee2aaSAndroid Build Coastguard Worker  return dm
236*c8dee2aaSAndroid Build Coastguard Worker
237*c8dee2aaSAndroid Build Coastguard Workerdef rasterize(path):
238*c8dee2aaSAndroid Build Coastguard Worker  ret = timeout(30, [PDFIUM_TEST, '--png', '--scale=%g' % (DPI / 72.0), path])
239*c8dee2aaSAndroid Build Coastguard Worker  if ret != 0:
240*c8dee2aaSAndroid Build Coastguard Worker    sys.stdout.write(
241*c8dee2aaSAndroid Build Coastguard Worker      '\nTIMEOUT OR ERROR [%d] "%s"\n' % (ret, printable_path(path)))
242*c8dee2aaSAndroid Build Coastguard Worker    return
243*c8dee2aaSAndroid Build Coastguard Worker  assert os.path.isfile(path + '.0.png')
244*c8dee2aaSAndroid Build Coastguard Worker
245*c8dee2aaSAndroid Build Coastguard Workerdef main(control_commitish):
246*c8dee2aaSAndroid Build Coastguard Worker  assert os.pardir == '..'  and '/' in [os.sep, os.altsep]
247*c8dee2aaSAndroid Build Coastguard Worker  assert test_exe(NINJA)
248*c8dee2aaSAndroid Build Coastguard Worker  assert test_exe(PDFIUM_TEST)
249*c8dee2aaSAndroid Build Coastguard Worker  os.chdir(os.path.dirname(__file__) + '/../..')
250*c8dee2aaSAndroid Build Coastguard Worker  control_worktree = checkout_worktree(control_commitish)
251*c8dee2aaSAndroid Build Coastguard Worker  tmpdir = tempfile.mkdtemp(prefix='skpdf_')
252*c8dee2aaSAndroid Build Coastguard Worker  exp = tmpdir + '/experim'
253*c8dee2aaSAndroid Build Coastguard Worker  con = tmpdir + '/control'
254*c8dee2aaSAndroid Build Coastguard Worker  build_and_run_dm(os.curdir, exp)
255*c8dee2aaSAndroid Build Coastguard Worker  dm = build_and_run_dm(control_worktree, con)
256*c8dee2aaSAndroid Build Coastguard Worker  image_diff_metric = build_skia(control_worktree, 'image_diff_metric')
257*c8dee2aaSAndroid Build Coastguard Worker
258*c8dee2aaSAndroid Build Coastguard Worker  out = sys.stdout
259*c8dee2aaSAndroid Build Coastguard Worker  common_paths = get_common_paths([con, exp], '.pdf')
260*c8dee2aaSAndroid Build Coastguard Worker  out.write('\nNumber of PDFs: %d\n\n' % len(common_paths))
261*c8dee2aaSAndroid Build Coastguard Worker  def compare_identical(path):
262*c8dee2aaSAndroid Build Coastguard Worker    cpath, epath = (os.path.join(x, path) for x in (con, exp))
263*c8dee2aaSAndroid Build Coastguard Worker    if is_same(cpath, epath):
264*c8dee2aaSAndroid Build Coastguard Worker      remove(cpath, epath)
265*c8dee2aaSAndroid Build Coastguard Worker      return True
266*c8dee2aaSAndroid Build Coastguard Worker    return False
267*c8dee2aaSAndroid Build Coastguard Worker  identical_count = shardsum(compare_identical, common_paths)
268*c8dee2aaSAndroid Build Coastguard Worker  out.write('Number of identical PDFs: %d\n\n' % identical_count)
269*c8dee2aaSAndroid Build Coastguard Worker
270*c8dee2aaSAndroid Build Coastguard Worker  differing_paths = get_common_paths([con, exp], '.pdf')
271*c8dee2aaSAndroid Build Coastguard Worker  if not differing_paths:
272*c8dee2aaSAndroid Build Coastguard Worker    out.write('All PDFs are the same!\n')
273*c8dee2aaSAndroid Build Coastguard Worker    sys.exit(0)
274*c8dee2aaSAndroid Build Coastguard Worker  out.write('Number of differing PDFs: %d\n' % len(differing_paths))
275*c8dee2aaSAndroid Build Coastguard Worker  for p in differing_paths:
276*c8dee2aaSAndroid Build Coastguard Worker    out.write('  %s\n' % printable_path(tmpdir + '/*/' + p))
277*c8dee2aaSAndroid Build Coastguard Worker  out.write('\n')
278*c8dee2aaSAndroid Build Coastguard Worker  shard(rasterize,
279*c8dee2aaSAndroid Build Coastguard Worker        [os.path.join(x, p) for p in differing_paths for x in [con, exp]])
280*c8dee2aaSAndroid Build Coastguard Worker
281*c8dee2aaSAndroid Build Coastguard Worker  common_pngs = get_common_paths([con, exp], '.pdf.0.png')
282*c8dee2aaSAndroid Build Coastguard Worker  identical_count = shardsum(compare_identical, common_pngs)
283*c8dee2aaSAndroid Build Coastguard Worker  out.write('Number of PDFs that rasterize the same: %d\n\n'
284*c8dee2aaSAndroid Build Coastguard Worker            % identical_count)
285*c8dee2aaSAndroid Build Coastguard Worker
286*c8dee2aaSAndroid Build Coastguard Worker  differing_pngs = get_common_paths([con, exp], '.pdf.0.png')
287*c8dee2aaSAndroid Build Coastguard Worker  if not differing_pngs:
288*c8dee2aaSAndroid Build Coastguard Worker    out.write('All PDFs rasterize the same!\n')
289*c8dee2aaSAndroid Build Coastguard Worker    sys.exit(0)
290*c8dee2aaSAndroid Build Coastguard Worker  out.write('Number of PDFs that rasterize differently: %d\n'
291*c8dee2aaSAndroid Build Coastguard Worker            % len(differing_pngs))
292*c8dee2aaSAndroid Build Coastguard Worker  for p in differing_pngs:
293*c8dee2aaSAndroid Build Coastguard Worker    out.write('  %s\n' % printable_path(tmpdir + '/*/' + p))
294*c8dee2aaSAndroid Build Coastguard Worker  out.write('\n')
295*c8dee2aaSAndroid Build Coastguard Worker
296*c8dee2aaSAndroid Build Coastguard Worker  scores = dict()
297*c8dee2aaSAndroid Build Coastguard Worker  def compare_differing_pngs(path):
298*c8dee2aaSAndroid Build Coastguard Worker    cpath, epath = (os.path.join(x, path) for x in (con, exp))
299*c8dee2aaSAndroid Build Coastguard Worker    s = float(subprocess.check_output([image_diff_metric, cpath, epath]))
300*c8dee2aaSAndroid Build Coastguard Worker    indicator = '.' if s < 0.001 else ':' if s < 0.01 else '!'
301*c8dee2aaSAndroid Build Coastguard Worker    sys.stdout.write(indicator)
302*c8dee2aaSAndroid Build Coastguard Worker    sys.stdout.flush()
303*c8dee2aaSAndroid Build Coastguard Worker    scores[path] = s
304*c8dee2aaSAndroid Build Coastguard Worker  shard(compare_differing_pngs, differing_pngs)
305*c8dee2aaSAndroid Build Coastguard Worker  paths = sorted(scores.iterkeys(), key=lambda p: -scores[p])
306*c8dee2aaSAndroid Build Coastguard Worker  out.write('\n\n')
307*c8dee2aaSAndroid Build Coastguard Worker  for p in paths:
308*c8dee2aaSAndroid Build Coastguard Worker    pdfpath = printable_path(tmpdir + '/*/' + p.replace('.0.png', ''))
309*c8dee2aaSAndroid Build Coastguard Worker    out.write('  %6.4f  %s\n' % (scores[p], pdfpath))
310*c8dee2aaSAndroid Build Coastguard Worker  out.write('\n')
311*c8dee2aaSAndroid Build Coastguard Worker
312*c8dee2aaSAndroid Build Coastguard Worker  errors = []
313*c8dee2aaSAndroid Build Coastguard Worker  rc = re.compile('^' + PDF_CONFIG + r'/([^/]*)/([^/]*)\.pdf\.0\.png$')
314*c8dee2aaSAndroid Build Coastguard Worker  for p in paths:
315*c8dee2aaSAndroid Build Coastguard Worker    m = rc.match(p)
316*c8dee2aaSAndroid Build Coastguard Worker    assert(m)
317*c8dee2aaSAndroid Build Coastguard Worker    source, name = m.groups()
318*c8dee2aaSAndroid Build Coastguard Worker    errors.append((source, name, scores[p]))
319*c8dee2aaSAndroid Build Coastguard Worker
320*c8dee2aaSAndroid Build Coastguard Worker  for source in SOURCES:
321*c8dee2aaSAndroid Build Coastguard Worker    os.makedirs(os.path.join(con, REFERENCE_BACKEND, source))
322*c8dee2aaSAndroid Build Coastguard Worker  dm_args = [dm, '--src'] + SOURCES + [
323*c8dee2aaSAndroid Build Coastguard Worker             '--config', REFERENCE_BACKEND, '-w', con, '-m'] + [
324*c8dee2aaSAndroid Build Coastguard Worker             '^%s$' % name for _, name, _ in errors]
325*c8dee2aaSAndroid Build Coastguard Worker  check_call(dm_args, cwd=control_worktree)
326*c8dee2aaSAndroid Build Coastguard Worker
327*c8dee2aaSAndroid Build Coastguard Worker  report = tmpdir + '/report.html'
328*c8dee2aaSAndroid Build Coastguard Worker  with open(report, 'w') as o:
329*c8dee2aaSAndroid Build Coastguard Worker    o.write(HTML_HEAD)
330*c8dee2aaSAndroid Build Coastguard Worker    o.write('c="%s/";\n' % os.path.relpath(con, tmpdir))
331*c8dee2aaSAndroid Build Coastguard Worker    o.write('e="%s/";\n' % os.path.relpath(exp, tmpdir))
332*c8dee2aaSAndroid Build Coastguard Worker    o.write('z=[\n')
333*c8dee2aaSAndroid Build Coastguard Worker    for source, name, score in errors:
334*c8dee2aaSAndroid Build Coastguard Worker      gt = REFERENCE_BACKEND + '/' + source + '/' + name + '.png'
335*c8dee2aaSAndroid Build Coastguard Worker      p = '%s/%s/%s.pdf.0.png' % (PDF_CONFIG, source, name)
336*c8dee2aaSAndroid Build Coastguard Worker      desc = '%s | %s | %g' % (source, name, score)
337*c8dee2aaSAndroid Build Coastguard Worker      o.write('["%s","%s","%s"],\n' % (p, gt, desc))
338*c8dee2aaSAndroid Build Coastguard Worker    o.write(HTML_TAIL)
339*c8dee2aaSAndroid Build Coastguard Worker  out.write(printable_path(report) + '\n')
340*c8dee2aaSAndroid Build Coastguard Worker  sysopen(report)
341*c8dee2aaSAndroid Build Coastguard Worker
342*c8dee2aaSAndroid Build Coastguard Workerif __name__ == '__main__':
343*c8dee2aaSAndroid Build Coastguard Worker  if len(sys.argv) != 2:
344*c8dee2aaSAndroid Build Coastguard Worker    USAGE = ('\nusage:\n  {0} COMMIT_OR_BRANCH_TO_COMPARE_TO\n\n'
345*c8dee2aaSAndroid Build Coastguard Worker             'e.g.:\n  {0} HEAD\nor\n  {0} HEAD~1\n\n')
346*c8dee2aaSAndroid Build Coastguard Worker    sys.stderr.write(USAGE.format(sys.argv[0]))
347*c8dee2aaSAndroid Build Coastguard Worker    sys.exit(1)
348*c8dee2aaSAndroid Build Coastguard Worker  main(sys.argv[1])
349