def main(font_path_a, font_path_b, specimen_path): generator = hb_input.HbInputGenerator(ttLib.TTFont(font_path_a)) inputs_a = generator.all_inputs(warn=True) generator = hb_input.HbInputGenerator(ttLib.TTFont(font_path_b)) inputs_b = set(generator.all_inputs(warn=True)) to_ignore = ('\00', '\02') to_replace = (('&', '&'), ('<', '<'), ('>', '>')) out_lines = ['<html>'] for features, text in [i for i in inputs_a if i in inputs_b]: if any(char in text for char in to_ignore): continue for old, new in to_replace: text = text.replace(old, new) style = '' if features: style = (' style="font-feature-settings: %s;"' % ', '.join("'%s'" % f for f in features)) out_lines.append('<p%s>%s</p>' % (style, text)) out_lines.append('</html>') out_text = '\n'.join(out_lines) with open(specimen_path, 'w') as out_file: out_file.write(out_text.encode('utf-8'))
def find_rendered_diffs(self, font_size=128, render_path=None): """Find diffs of glyphs as rendered by harfbuzz.""" hb_input_generator_a = hb_input.HbInputGenerator(self.font_a) hb_input_generator_b = hb_input.HbInputGenerator(self.font_b) if render_path: font_name, _ = os.path.splitext(self.basepath) render_path = os.path.join(render_path, font_name) if not os.path.exists(render_path): os.makedirs(render_path) self.build_names() diffs = [] for name in self.names: class_a = self.gdef_a.get(name, GDEF_UNDEF) class_b = self.gdef_b.get(name, GDEF_UNDEF) if GDEF_MARK in (class_a, class_b) and class_a != class_b: self.stats['gdef_mark_mismatch'].append( (self.basepath, name, GDEF_LABELS[class_a], GDEF_LABELS[class_b])) continue width_a = self.glyph_set_a[name].width width_b = self.glyph_set_b[name].width zwidth_a = width_a == 0 zwidth_b = width_b == 0 if zwidth_a != zwidth_b: self.stats['zero_width_mismatch'].append( (self.basepath, name, width_a, width_b)) continue hb_args_a = hb_input_generator_a.input_from_name(name, pad=zwidth_a) hb_args_b = hb_input_generator_b.input_from_name(name, pad=zwidth_b) if hb_args_a != hb_args_b: self.stats['input_mismatch'].append( (self.basepath, name, hb_args_a, hb_args_b)) continue # ignore unreachable characters if not hb_args_a: self.stats['untested'].append((self.basepath, name)) continue features, text = hb_args_a # ignore null character if unichr(0) in text: continue img_file_a = StringIO.StringIO( subprocess.check_output([ 'hb-view', '--font-size=%d' % font_size, '--features=%s' % ','.join(features), self.path_a, text ])) img_file_b = StringIO.StringIO( subprocess.check_output([ 'hb-view', '--font-size=%d' % font_size, '--features=%s' % ','.join(features), self.path_b, text ])) img_a = Image.open(img_file_a) img_b = Image.open(img_file_b) width_a, height_a = img_a.size width_b, height_b = img_b.size data_a = img_a.getdata() data_b = img_b.getdata() img_file_a.close() img_file_b.close() width, height = max(width_a, width_b), max(height_a, height_b) offset_ax = (width - width_a) // 2 offset_ay = (height - height_a) // 2 offset_bx = (width - width_b) // 2 offset_by = (height - height_b) // 2 diff = 0 for y in range(height): for x in range(width): ax, ay = x - offset_ax, y - offset_ay bx, by = x - offset_bx, y - offset_by if (ax < 0 or bx < 0 or ax >= width_a or bx >= width_b or ay < 0 or by < 0 or ay >= height_a or by >= height_b): diff += 1 else: diff += abs(data_a[ax + ay * width_a] - data_b[bx + by * width_b]) / 255 if self.ratio_diffs: diff /= (width * height) if render_path and diff > self.diff_threshold: img_cmp = Image.new('RGB', (width, height)) data_cmp = list(img_cmp.getdata()) self._project(data_a, width_a, height_a, data_cmp, width, height, 1) self._project(data_b, width_b, height_b, data_cmp, width, height, 0) for y in range(height): for x in range(width): i = x + y * width r, g, b = data_cmp[i] assert b == 0 data_cmp[i] = r, g, min(r, g) img_cmp.putdata(data_cmp) img_cmp.save(self._rendered_png(render_path, name)) diffs.append((name, diff)) mismatched = {} for name, diff in diffs: if diff > self.diff_threshold: mismatched[name] = diff stats = self.stats['compared'] for name, diff in mismatched.items(): stats.append((diff, name, self.basepath))
def find_rendered_diffs(self, font_size=256, render_path=None): """Find diffs of glyphs as rendered by harfbuzz + image magick.""" hb_input_generator_a = hb_input.HbInputGenerator(self.font_a) hb_input_generator_b = hb_input.HbInputGenerator(self.font_b) a_png_file = tempfile.NamedTemporaryFile() a_png = a_png_file.name b_png_file = tempfile.NamedTemporaryFile() b_png = b_png_file.name cmp_png_file = tempfile.NamedTemporaryFile() cmp_png = cmp_png_file.name diffs_file = tempfile.NamedTemporaryFile() diffs_filename = diffs_file.name self.build_names() for name in self.names: class_a = self.gdef_a.get(name, GDEF_UNDEF) class_b = self.gdef_b.get(name, GDEF_UNDEF) if GDEF_MARK in (class_a, class_b) and class_a != class_b: self.stats['gdef_mark_mismatch'].append( (self.basepath, name, GDEF_LABELS[class_a], GDEF_LABELS[class_b])) continue width_a = self.glyph_set_a[name].width width_b = self.glyph_set_b[name].width zwidth_a = width_a == 0 zwidth_b = width_b == 0 if zwidth_a != zwidth_b: self.stats['zero_width_mismatch'].append( (self.basepath, name, width_a, width_b)) continue hb_args_a = hb_input_generator_a.input_from_name(name, pad=zwidth_a) hb_args_b = hb_input_generator_b.input_from_name(name, pad=zwidth_b) if hb_args_a != hb_args_b: self.stats['input_mismatch'].append( (self.basepath, name, hb_args_a, hb_args_b)) continue # ignore unreachable characters if not hb_args_a: self.stats['untested'].append((self.basepath, name)) continue features, text = hb_args_a # ignore null character if unichr(0) in text: continue with open(diffs_filename, 'a') as ofile: ofile.write('%s\n' % name) subprocess.call([ 'hb-view', '--font-size=%d' % font_size, '--output-file=%s' % a_png, '--features=%s' % ','.join(features), self.path_a, text ]) subprocess.call([ 'hb-view', '--font-size=%d' % font_size, '--output-file=%s' % b_png, '--features=%s' % ','.join(features), self.path_b, text ]) img_info = subprocess.check_output(['identify', a_png]).split() assert img_info[0] == a_png and img_info[1] == 'PNG' subprocess.call([ 'convert', '-gravity', 'center', '-background', 'black', '-extent', img_info[2], b_png, b_png ]) if render_path: glyph_filename = re.sub(r'([A-Z_])', r'\1_', name) + '.png' output_png = os.path.join(render_path, glyph_filename) # see for a discussion of this rendering technique: # https://github.com/googlei18n/nototools/issues/162#issuecomment-175885431 subprocess.call([ 'convert', '(', a_png, '-colorspace', 'gray', ')', '(', b_png, '-colorspace', 'gray', ')', '(', '-clone', '0-1', '-compose', 'darken', '-composite', ')', '-channel', 'RGB', '-combine', output_png ]) with open(diffs_filename, 'a') as ofile: subprocess.call( ['compare', '-metric', 'AE', a_png, b_png, cmp_png], stderr=ofile) ofile.write('\n') with open(diffs_filename) as ifile: lines = [l.strip() for l in ifile.readlines() if l.strip()] diffs = [(lines[i], lines[i + 1]) for i in range(0, len(lines), 2)] mismatched = {} for name, diff in diffs: if int(diff) != 0: mismatched[name] = int(diff) stats = self.stats['compared'] for name, diff in mismatched.items(): stats.append((diff, name, self.basepath))
def find_rendered_diffs(self, font_size=256, render_path=None): """Find diffs of glyphs as rendered by harfbuzz + image magick.""" self.build_names() self.build_reverse_cmap() hb_input_generators = [ hb_input.HbInputGenerator(p, self.reverse_cmap) for p in self.paths ] path_a, path_b = self.paths ordered_names = list(self.names) a_png = self._make_tmp_path() b_png = self._make_tmp_path() cmp_png = self._make_tmp_path() diffs_filename = self._make_tmp_path() for name in ordered_names: hb_args, hb_args_b = [ input_generator.input_from_name(name) for input_generator in hb_input_generators ] assert hb_args == hb_args_b # ignore unreachable characters if not hb_args: self.stats['untested'].append(name) continue features, text = hb_args # ignore null character if text == unichr(0): continue with open(diffs_filename, 'a') as ofile: ofile.write('%s\n' % name) subprocess.call([ 'hb-view', '--font-size=%d' % font_size, '--output-file=%s' % a_png, '--features=%s' % ','.join(features), path_a, text ]) subprocess.call([ 'hb-view', '--font-size=%d' % font_size, '--output-file=%s' % b_png, '--features=%s' % ','.join(features), path_b, text ]) img_info = subprocess.check_output(['identify', a_png]).split() assert img_info[0] == a_png and img_info[1] == 'PNG' subprocess.call([ 'convert', '-gravity', 'center', '-background', 'black', '-extent', img_info[2], b_png, b_png ]) if render_path: output_png = os.path.join(render_path, name + '.png') # see for a discussion of this rendering technique: # https://github.com/googlei18n/nototools/issues/162#issuecomment-175885431 subprocess.call([ 'convert', '(', a_png, '-colorspace', 'gray', ')', '(', b_png, '-colorspace', 'gray', ')', '(', '-clone', '0-1', '-compose', 'darken', '-composite', ')', '-channel', 'RGB', '-combine', output_png ]) with open(diffs_filename, 'a') as ofile: subprocess.call( ['compare', '-metric', 'AE', a_png, b_png, cmp_png], stderr=ofile) with open(diffs_filename) as ifile: lines = ifile.readlines() diffs = [(lines[i].strip(), lines[i + 1].strip()) for i in range(0, len(lines), 2)] os.remove(a_png) os.remove(b_png) os.remove(cmp_png) os.remove(diffs_filename) mismatched = {} for name, diff in diffs: if int(diff) != 0: mismatched[name] = int(diff) stats = self.stats['compared'] for name, diff in mismatched.items(): stats.append((diff, name, self.basepath))