#!/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 sanity check whitespace chars in fonts.

Not in sanity_check because it can yield false positives. For example,
Material Icons correctly doesn't include a space. If metadata awareness
was added it could move into sanity_check.

"""
from __future__ import print_function
import contextlib
import os

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


def _LoadGlyf(font, char, name, problems):
  """Loads a glyf and expands (populates fields) it.

  Args:
    font: A TTFont.
    char: int id of char, e.g. ord('C').
    name: name to use in problems string describing char. e.g. SPACE.
    problems: set to add problems to.
  Returns:
    2-tuple of (cmap_key, glyph). (None, None) if a problem occurred. Adds
    to problems if a problem occurred.
  """
  if 'glyf' not in font:
    problems.add('NO_GLYF_TABLE')
    return (None, None)

  cmap = fonts.UnicodeCmapTables(font).next().cmap
  if char not in cmap:
    problems.add('NO_%s' % name)
    return (None, None)

  key = cmap[char]
  glyph = font['glyf'].glyphs[key]
  glyph.expand(font['glyf'])

  return (key, glyph)


def _HasInk(font, glyph_name):
  """Checks if specified glyph has any ink.

  That is, that it has at least one defined contour associated. Composites are
  considered to have ink if any of their components have ink.

  Args:
    font: A TTFont that has a 'glyf' table.
    glyph_name: The name of the glyph to check for ink.
  Returns:
    True if the font has at least one contour associated with it.
  """
  glyph = font['glyf'].glyphs[glyph_name]
  glyph.expand(font['glyf'])

  if not glyph.isComposite():
    if glyph.numberOfContours == 0:
      return False
    (coords, _, _) = glyph.getCoordinates(font['glyf'])
    # you need at least 3 points to draw
    return len(coords) > 2

  # composite is blank if composed of blanks
  # if you setup a font with cycles you are just a bad person
  for glyph_name in glyph.getComponentNames(glyph.components):
    if _HasInk(font, glyph_name):
      return True

  return False


def _CheckFont(font):
  """Inspects a font for space/nbsp issues.

  Args:
    font: A TTFont.
  Returns:
    A set of strings describing problems found in the font. Empty set if none.
  """
  problems = set()

  (space_cmap, _) = _LoadGlyf(font, 0x0020, 'SPACE', problems)
  (nbsp_cmap, _) = _LoadGlyf(font, 0x00A0, 'NBSP', problems)

  if nbsp_cmap and _HasInk(font, nbsp_cmap):
    problems.add('NBSP_HAS_INK')

  if space_cmap and _HasInk(font, space_cmap):
    problems.add('SPACE_HAS_INK')

  if nbsp_cmap and space_cmap:
    if font['hmtx'][nbsp_cmap][0] != font['hmtx'][space_cmap][0]:
      problems.add('SPACE_NBSP_WIDTH_MISMATCH')

  return set(problems)


def main(argv):
  for filename in argv[1:]:
    with contextlib.closing(ttLib.TTFont(filename)) as font:
      problems = _CheckFont(font)
    if not problems:
      problems.add('OK')
    print('{:48} {}'.format(os.path.basename(filename),
                            ','.join(sorted(problems))))

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