#!/usr/bin/python3
#
# Copyright 2017 Google Inc. 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.
#
"""Utility to compare two font files.

Prints change in glyph count by supported subset and change in size by table.
This is helpful for understanding what is happening when we get a new version of
a font (why is Roboto2 3x larger than Roboto?).

Sample output (abriged run for roboto vs roboto2, at the time slightly smaller):
Subset Coverage Change (codepoints)
  cyrillic -1 (316/320 => 315/320)
  cyrillic-ext -1 (491/653 => 490/653)
  greek -1 (290/363 => 289/363)
  greek-ext -1 (291/619 => 290/619)
  latin -1 (215/219 => 214/219)
  latin-ext -11 (389/1029 => 378/1029)
  vietnamese -1 (300/304 => 299/304)
  all -13 (896 => 883)
Roboto-Regular.ttf (162876) vs Roboto2-Regular.ttf (145256) (-17620)
  Table Changes (delta bytes, from=>to, % change)
    BASE, +0, 0=>0, 0.0%
    CFF , +0, 0=>0, 0.0%
    DSIG, +0, 0=>0, 0.0%
    FFTM, +0, 0=>0, 0.0%
    GDEF, -516, 580=>64, -0.3%
    GPOS, -146, 21028=>20882, -0.1%
    GSUB, -5052, 6120=>1068, -3.1%
    LTSH, +0, 0=>0, 0.0%
    OS/2, +0, 96=>96, 0.0%
    VORG, +0, 0=>0, 0.0%
    cmap, -2634, 4808=>2174, -1.6%
    cvt , +68, 76=>144, 0.0%
    fpgm, +2483, 444=>2927, 1.5%
    gasp, -4, 12=>8, -0.0%
    glyf, -19546, 119606=>100060, -12.0%
    hdmx, -1260, 1260=>0, -0.8%
    head, +0, 54=>54, 0.0%
    hhea, +0, 36=>36, 0.0%
    hmtx, -712, 5000=>4288, -0.4%
    kern, +0, 0=>0, 0.0%
    loca, -356, 2502=>2146, -0.2%
    maxp, +0, 32=>32, 0.0%
    name, +708, 664=>1372, 0.4%
    post, +9437, 32=>9469, 5.8%
    prep, -81, 219=>138, -0.0%
    vhea, +0, 0=>0, 0.0%
    vmtx, +0, 0=>0, 0.0%
    TOTAL, -17611, 162569=>144958, -10.8%
"""

import errno
import os
import sys

from fontTools.ttLib import sfnt
from absl import flags, app
from gftools.util import google_fonts as fonts


FLAGS = flags.FLAGS
flags.DEFINE_boolean('diff_tables', True, 'Whether to print table size diffs')
flags.DEFINE_boolean('diff_coverage', True, 'Whether to print coverage diffs')

_KNOWN_TABLES = ('BASE', 'CFF ', 'DSIG', 'GDEF', 'GPOS', 'GSUB', 'LTSH',
                 'OS/2', 'VORG', 'cmap', 'cvt ', 'fpgm', 'gasp', 'glyf', 'hdmx',
                 'head', 'hhea', 'hmtx', 'loca', 'maxp', 'name', 'post', 'prep',
                 'FFTM', 'kern', 'vhea', 'vmtx')


def CompareSize(font_filename1, font_filename2):
  """Prints a size comparison for two fonts.

  If so flagged (--diff_tables), prints per-table size change.

  Args:
    font_filename1: The first font to compare.
    font_filename2: The second font to compare.
  Returns:
    String describing size differences.
  Raises:
    OSError: If either argument doesn't point to a file. errno.ENOENT.
  """
  if not (os.path.isfile(font_filename1) and os.path.isfile(font_filename2)):
    raise OSError(errno.ENOENT, 'Missing at least one of %s and %s' % (
        os.path.basename(font_filename1), os.path.basename(font_filename2)))

  font_sz1 = os.stat(font_filename1).st_size
  font_sz2 = os.stat(font_filename2).st_size
  result = '%s (%d) vs %s (%d) (%+d)\n' % (
      os.path.basename(font_filename1), font_sz1,
      os.path.basename(font_filename2), font_sz2, font_sz2 - font_sz1)

  if FLAGS.diff_tables:
    result += DiffTables(font_filename1, font_filename2)

  return result


def DiffTables(font_filename1, font_filename2):
  """Prints a table-by-table size comparison of two fonts.

  Args:
    font_filename1: The first font to compare.
    font_filename2: The second font to compare.
  Returns:
    String describing size difference. One line per unique table in either font.
  """
  result = ['    Table    Changes  Delta-Bytes(from=>to)  % Change']
  result.append('    -------------------------------------------------')
  sfnt1 = sfnt.SFNTReader(open(font_filename1, 'rb'))
  sfnt2 = sfnt.SFNTReader(open(font_filename2, 'rb'))

  font_sz1 = os.stat(font_filename1).st_size

  sum_tables1 = 0
  sum_tables2 = 0

  table_l1_l2s = []
  for t in fonts.UniqueSort(sfnt1.tables, sfnt2.tables, _KNOWN_TABLES):
    table1_sz = sfnt1.tables[t].length if t in sfnt1 else 0
    table2_sz = sfnt2.tables[t].length if t in sfnt2 else 0
    sum_tables1 += table1_sz
    sum_tables2 += table2_sz
    table_l1_l2s.append((t, table1_sz, table2_sz))

  for (table, table1_sz, table2_sz) in table_l1_l2s:
    delta_pct = float(table2_sz - table1_sz) / font_sz1 * 100
    result.append('    %s  %+6d      %06d => %06d %+10.1f%%' % (
        table, table2_sz - table1_sz, table1_sz, table2_sz, delta_pct))

  delta_pct = float(sum_tables2 - sum_tables1) / font_sz1 * 100
  result.append('    TOTAL %+6d      %06d => %06d %+10.1f%%' % (
      sum_tables2 - sum_tables1, sum_tables1, sum_tables2, delta_pct))

  return '\n'.join(result)


def DiffCoverage(font_filename1, font_filename2, subset):
  """Prints a comparison of the coverage of a given subset by two fonts.

  Args:
    font_filename1: The first font to compare.
    font_filename2: The second font to compare.
    subset: The lowercase name of the subset to compare coverage of.
  """
  f1cps = fonts.CodepointsInFont(font_filename1)
  f2cps = fonts.CodepointsInFont(font_filename2)

  if subset != 'all':
    subset_cps = fonts.CodepointsInSubset(subset)
    f1cps &= subset_cps
    f2cps &= subset_cps
  else:
    subset_cps = None

  subset_cp_str = ('/%d' % len(subset_cps)) if subset_cps is not None else ''

  print('  %s %+d (%d%s => %d%s)' % (
      subset, len(f2cps) - len(f1cps), len(f1cps), subset_cp_str, len(f2cps),
      subset_cp_str))


def CompareDirs(font1, font2):
  """Compares fonts by assuming font1/2 are dirs containing METADATA.pb."""

  m1 = fonts.Metadata(font1)
  m2 = fonts.Metadata(font2)

  subsets_to_compare = fonts.UniqueSort(m1.subsets, m2.subsets)
  subsets_to_compare.remove('menu')
  subsets_to_compare.append('all')

  font_filename1 = os.path.join(font1, fonts.RegularWeight(m1))
  font_filename2 = os.path.join(font2, fonts.RegularWeight(m2))

  if FLAGS.diff_coverage:
    print('Subset Coverage Change (codepoints)')
    for subset in subsets_to_compare:
      DiffCoverage(font_filename1, font_filename2, subset)

  print(CompareSize(font_filename1, font_filename2))


def CompareFiles(font1, font2):
  """Compares fonts assuming font1/2 are font files."""
  print(CompareSize(font1, font2))


def main(_):
  if len(sys.argv) < 3:
    raise app.UsageError('Must pass at least two arguments, font file or font'
                         ' dir to diff')

  font1 = sys.argv[1]
  font2 = sys.argv[2]
  dirs = os.path.isdir(font1) and os.path.isdir(font2)
  files = os.path.isfile(font1) and os.path.isfile(font2)

  if not dirs and not files:
    print('%s and %s must both point to directories or font files' % (
        font1, font2))
    sys.exit(1)

  if dirs:
    CompareDirs(font1, font2)

  if files:
    CompareFiles(font1, font2)


if __name__ == '__main__':
  app.run(main)
