text: #!/usr/bin/python
  # coding: utf-8
  # Copyright 2013 The Font Bakery Authors. All Rights Reserved.
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  #     http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  #
  # See AUTHORS.txt for the list of Authors and LICENSE.txt for the License.
  #
  # Based on http://typophile.com/node/13081
  # Also see http://typophile.com/node/13081
  from __future__ import print_function
  
  import collections
  import os
  
  import sys
  
  if sys.version_info.major >= 3:
      from io import StringIO
  else:
      from StringIO import StringIO
  
  from fontTools.ttLib import TTLibError
  
  
  def metricview(fonts):
      from bakery_cli.ttfont import Font
      view = TextMetricsView()
      for f in fonts:
          try:
              metrics = Font(f)
          except TTLibError as ex:
              print("ERROR: %s" % ex)
              continue
          view.add_metric(os.path.basename(f), metrics)
      view.print_metrics()
      return view.get_contents()
  
  
  def get_metric_view(fonts):
      from bakery_cli.ttfont import Font
      view = TextMetricsView()
      for f in fonts:
          try:
              metrics = Font(f)
          except TTLibError as ex:
              print("ERROR: %s" % ex)
              continue
          view.add_metric(os.path.basename(f), metrics)
      view.print_metrics()
      return view
  
  
  class TextMetricsView(object):
  
      def __init__(self):
          self.outstream = StringIO()
  
          self._its_metrics_header = ['Parameter          ']
          # first column has a length of largest parameter
          # named OS/2.sTypoDescender
          self._its_metrics = collections.OrderedDict([
              ('ymax', []),
              ('hhea.ascent', []),
              ('OS/2.sTypoAscender', []),
              ('OS/2.usWinAscent', []),
              ('ymin', []),
              ('hhea.descent', []),
              ('OS/2.sTypoDescender', []),
              ('OS/2.usWinDescent', []),
              ('hhea.lineGap', []),
              ('OS/2.sTypoLineGap', []),
              ('hhea total', []),
              ('typo total', []),
              ('win total', []),
              ('UPM:Heights', []),
              ('UPM:Heights %', [])
          ])
          self._inconsistent = set()
          self._inconsistent_table = {}
          self._warnings = []
  
          self.glyphs = collections.OrderedDict()
  
      def add_to_table(self, fontname, key, value):
          if self._its_metrics[key] and value not in self._its_metrics[key]:
              self._inconsistent.add(key)
  
          if key not in self._inconsistent_table:
              self._inconsistent_table[key] = []
  
          # lookup row with value and append fontname to `fonts` key, eg.:
          # {'hhea.ascent': [{'value': 390,
          #                   'fonts': ['fontname.ttf', 'fontname2.ttf']}]}
          #
          # It looks like json groupped by metrics key
          inconsistentRow = {}
          for r in self._inconsistent_table[key]:
              if r['value'] == value:
                  inconsistentRow = r
  
          if not inconsistentRow:
              inconsistentRow = {'value': value, 'fonts': []}
              self._inconsistent_table[key].append(inconsistentRow)
  
          inconsistentRow['fonts'].append(fontname)
  
          self._its_metrics[key].append(value)
  
      def add_metric(self, font_name, vmet):
          ymin, ymax = vmet.get_bounding()
          self._its_metrics_header.append(font_name)
          self.add_to_table(font_name, 'hhea.ascent', vmet.ascents.hhea)
          self.add_to_table(font_name, 'OS/2.sTypoAscender', vmet.ascents.os2typo)
          self.add_to_table(font_name, 'OS/2.usWinAscent', vmet.ascents.os2win)
          self.add_to_table(font_name, 'hhea.descent', vmet.descents.hhea)
          self.add_to_table(font_name, 'OS/2.sTypoDescender', vmet.descents.os2typo)
          self.add_to_table(font_name, 'OS/2.usWinDescent', vmet.descents.os2win)
          self.add_to_table(font_name, 'hhea.lineGap', vmet.linegaps.hhea)
          self.add_to_table(font_name, 'OS/2.sTypoLineGap', vmet.linegaps.os2typo)
          self._its_metrics['ymax'].append(ymax)
          self._its_metrics['ymin'].append(ymin)
  
          value = abs(ymin) + ymax
          upm = '%s:%s' % (vmet.get_upm_height(), value)
          self._its_metrics['UPM:Heights'].append(upm)
  
          value = (value / float(vmet.get_upm_height())) * 100
          self._its_metrics['UPM:Heights %'].append('%d %%' % value)
  
          hhea_total = vmet.ascents.hhea + abs(vmet.descents.hhea) + vmet.linegaps.hhea
          self._its_metrics['hhea total'].append(hhea_total)
  
          typo_total = vmet.ascents.os2typo + abs(vmet.descents.os2typo) + vmet.linegaps.os2typo
          self._its_metrics['typo total'].append(typo_total)
  
          win_total = vmet.ascents.os2win + abs(vmet.descents.os2win)
          self._its_metrics['win total'].append(win_total)
  
          if len(set([typo_total, hhea_total, win_total])) > 1:
              self._warnings.append('%s has NOT even heights' % font_name)
  
          self.glyphs[font_name] = vmet.get_highest_and_lowest()
  
      def print_metrics(self):
          self.print_warnings()
          self.print_metrics_table()
          self.print_high_glyphs()
          self.print_low_glyphs()
          self.print_inconsistent_table()
  
      def print_warnings(self):
          if self._inconsistent:
              _ = 'WARNING: Inconsistent {}'
              print(_.format(' '.join([str(x) for x in self._inconsistent])),
                    end='\n\n', file=self.outstream)
  
          if self._warnings:
              for warn in self._warnings:
                  print('WARNING: %s' % warn, file=self.outstream)
  
      def print_metrics_table(self):
          formatstring = ''
          for k in self._its_metrics_header:
              print(('{:<%s}' % (len(k) + 4)).format(k), end='', file=self.outstream)
              formatstring += '{:<%s}' % (len(k) + 4)
  
          print(file=self.outstream)
          for k, values in self._its_metrics.items():
              print(formatstring.format(*([k] + values)), file=self.outstream)
  
      def print_high_glyphs(self):
          header_printed = False
          for font, glyphs in self.glyphs.items():
              if glyphs[0]:
                  if not header_printed:
                      print(file=self.outstream)
                      print('High Glyphs', file=self.outstream)
                      header_printed = True
                  print(font + ':', ' '.join(glyphs[0]), file=self.outstream)
  
      def print_low_glyphs(self):
          header_printed = False
          for font, glyphs in self.glyphs.items():
              if glyphs[1]:
                  if not header_printed:
                      print(file=self.outstream)
                      print('Low Glyphs', file=self.outstream)
                      header_printed = True
                  print(font + ':', ' '.join(glyphs[1]), file=self.outstream)
  
      def print_inconsistent_table(self):
          print(file=self.outstream)
          for metrickey, row in self._inconsistent_table.items():
              value = self.find_max_occurs_from_metrics_key(row)
  
              tbl = {}
              for r in row:
                  if r['value'] == value:
                      continue
                  if metrickey not in tbl:
                      tbl[metrickey] = []
                  tbl[metrickey] += r['fonts']
  
              for k, r in tbl.items():
                  print('WARNING: Inconsistent %s:' % k, ', '.join(r),
                        file=self.outstream)
  
      def find_max_occurs_from_metrics_key(self, metricvalues):
          result = 0
          occurs = 0
          if len(metricvalues) == 2:
              return metricvalues[1]['value']
          for v in metricvalues:
              if len(v['fonts']) > occurs:
                  occurs = len(v['fonts'])
                  result = v['value']
          return result
  
      def get_contents(self):
          self.outstream.seek(0)
          return self.outstream.read()
  