/*
//
// Copyright (c) 1993-2017 Robert McNeel & Associates. All rights reserved.
// OpenNURBS, Rhinoceros, and Rhino3D are registered trademarks of Robert
// McNeel & Associates.
//
// THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY.
// ALL IMPLIED WARRANTIES OF FITNESS FOR ANY PARTICULAR PURPOSE AND OF
// MERCHANTABILITY ARE HEREBY DISCLAIMED.
//				
// For complete openNURBS copyright information see <http://www.opennurbs.org>.
//
////////////////////////////////////////////////////////////////
*/

#include "opennurbs.h"

#if !defined(ON_COMPILING_OPENNURBS)
// This check is included in all opennurbs source .c and .cpp files to insure
// ON_COMPILING_OPENNURBS is defined when opennurbs source is compiled.
// When opennurbs source is being compiled, ON_COMPILING_OPENNURBS is defined 
// and the opennurbs .h files alter what is declared and how it is declared.
#error ON_COMPILING_OPENNURBS must be defined when compiling opennurbs
#endif

#if defined(OPENNURBS_FREETYPE_SUPPORT)
// Look in opennurbs_system_rumtime.h for the correct place to define OPENNURBS_FREETYPE_SUPPORT.
// Do NOT define OPENNURBS_FREETYPE_SUPPORT here or in your project setting ("makefile").

#include "opennurbs_internal_glyph.h"

// opennurbs uses FreeType to calculate font metric, glyph metric, and glyph outline information.

// FreeType Licensing:
//
//// Retrieved March 22, 2017 
//// https://www.freetype.org/freetype2/docs/index.html
////What is FreeType?
////
////FreeType is a software font engine that is designed to be small, efficient, 
////highly customizable, and portable while capable of producing high-quality 
////output (glyph images). It can be used in graphics libraries, display servers,
////font conversion tools, text image generation tools, and many other products as well.
////
////Note that FreeType is a font service and doesn't provide APIs to perform 
////higher-level features like text layout or graphics processing 
////(e.g., colored text rendering, ‘hollowing’, etc.). However, it greatly 
////simplifies these tasks by providing a simple, easy to use, and uniform
////interface to access the content of font files.
////
////FreeType is released under two open-source licenses: our own BSD-like
////FreeType License and the GNU Public License, Version 2. It can thus
////be used by any kind of projects, be they proprietary or not.
////
////Please note that ‘FreeType’ is also called ‘FreeType 2’, to
////distinguish it from the old, deprecated ‘FreeType 1’ library,
////a predecessor no longer maintained and supported.
////
//// http://git.savannah.gnu.org/cgit/freetype/freetype2.git/tree/docs/FTL.TXT
//// 
////                    The FreeType Project LICENSE
////                    ----------------------------
////
////                            2006-Jan-27
////
////                    Copyright 1996-2002, 2006 by
////          David Turner, Robert Wilhelm, and Werner Lemberg
////
////
////
////Introduction
////============
////
////  The FreeType  Project is distributed in  several archive packages;
////  some of them may contain, in addition to the FreeType font engine,
////  various tools and  contributions which rely on, or  relate to, the
////  FreeType Project.
////
////  This  license applies  to all  files found  in such  packages, and
////  which do not  fall under their own explicit  license.  The license
////  affects  thus  the  FreeType   font  engine,  the  test  programs,
////  documentation and makefiles, at the very least.
////
////  This  license   was  inspired  by  the  BSD,   Artistic,  and  IJG
////  (Independent JPEG  Group) licenses, which  all encourage inclusion
////  and  use of  free  software in  commercial  and freeware  products
////  alike.  As a consequence, its main points are that:
////
////    o We don't promise that this software works. However, we will be
////      interested in any kind of bug reports. (`as is' distribution)
////
////    o You can  use this software for whatever you  want, in parts or
////      full form, without having to pay us. (`royalty-free' usage)
////
////    o You may not pretend that  you wrote this software.  If you use
////      it, or  only parts of it,  in a program,  you must acknowledge
////      somewhere  in  your  documentation  that  you  have  used  the
////      FreeType code. (`credits')
////
////  We  specifically  permit  and  encourage  the  inclusion  of  this
////  software, with  or without modifications,  in commercial products.
////  We  disclaim  all warranties  covering  The  FreeType Project  and
////  assume no liability related to The FreeType Project.
////
////
////  Finally,  many  people  asked  us  for  a  preferred  form  for  a
////  credit/disclaimer to use in compliance with this license.  We thus
////  encourage you to use the following text:
////
////   """
////    Portions of this software are copyright © <year> The FreeType
////    Project (www.freetype.org).  All rights reserved.
////   """
////
////  Please replace <year> with the value from the FreeType version you
////  actually use.
////
////
////Legal Terms
////===========
////
////0. Definitions
////--------------
////
////  Throughout this license,  the terms `package', `FreeType Project',
////  and  `FreeType  archive' refer  to  the  set  of files  originally
////  distributed  by the  authors  (David Turner,  Robert Wilhelm,  and
////  Werner Lemberg) as the `FreeType Project', be they named as alpha,
////  beta or final release.
////
////  `You' refers to  the licensee, or person using  the project, where
////  `using' is a generic term including compiling the project's source
////  code as  well as linking it  to form a  `program' or `executable'.
////  This  program is  referred to  as  `a program  using the  FreeType
////  engine'.
////
////  This  license applies  to all  files distributed  in  the original
////  FreeType  Project,   including  all  source   code,  binaries  and
////  documentation,  unless  otherwise  stated   in  the  file  in  its
////  original, unmodified form as  distributed in the original archive.
////  If you are  unsure whether or not a particular  file is covered by
////  this license, you must contact us to verify this.
////
////  The FreeType  Project is copyright (C) 1996-2000  by David Turner,
////  Robert Wilhelm, and Werner Lemberg.  All rights reserved except as
////  specified below.
////
////1. No Warranty
////--------------
////
////  THE FREETYPE PROJECT  IS PROVIDED `AS IS' WITHOUT  WARRANTY OF ANY
////  KIND, EITHER  EXPRESS OR IMPLIED,  INCLUDING, BUT NOT  LIMITED TO,
////  WARRANTIES  OF  MERCHANTABILITY   AND  FITNESS  FOR  A  PARTICULAR
////  PURPOSE.  IN NO EVENT WILL ANY OF THE AUTHORS OR COPYRIGHT HOLDERS
////  BE LIABLE  FOR ANY DAMAGES CAUSED  BY THE USE OR  THE INABILITY TO
////  USE, OF THE FREETYPE PROJECT.
////
////2. Redistribution
////-----------------
////
////  This  license  grants  a  worldwide, royalty-free,  perpetual  and
////  irrevocable right  and license to use,  execute, perform, compile,
////  display,  copy,   create  derivative  works   of,  distribute  and
////  sublicense the  FreeType Project (in  both source and  object code
////  forms)  and  derivative works  thereof  for  any  purpose; and  to
////  authorize others  to exercise  some or all  of the  rights granted
////  herein, subject to the following conditions:
////
////    o Redistribution of  source code  must retain this  license file
////      (`FTL.TXT') unaltered; any  additions, deletions or changes to
////      the original  files must be clearly  indicated in accompanying
////      documentation.   The  copyright   notices  of  the  unaltered,
////      original  files must  be  preserved in  all  copies of  source
////      files.
////
////    o Redistribution in binary form must provide a  disclaimer  that
////      states  that  the software is based in part of the work of the
////      FreeType Team,  in  the  distribution  documentation.  We also
////      encourage you to put an URL to the FreeType web page  in  your
////      documentation, though this isn't mandatory.
////
////  These conditions  apply to any  software derived from or  based on
////  the FreeType Project,  not just the unmodified files.   If you use
////  our work, you  must acknowledge us.  However, no  fee need be paid
////  to us.
////
////3. Advertising
////--------------
////
////  Neither the  FreeType authors and  contributors nor you  shall use
////  the name of the  other for commercial, advertising, or promotional
////  purposes without specific prior written permission.
////
////  We suggest,  but do not require, that  you use one or  more of the
////  following phrases to refer  to this software in your documentation
////  or advertising  materials: `FreeType Project',  `FreeType Engine',
////  `FreeType library', or `FreeType Distribution'.
////
////  As  you have  not signed  this license,  you are  not  required to
////  accept  it.   However,  as  the FreeType  Project  is  copyrighted
////  material, only  this license, or  another one contracted  with the
////  authors, grants you  the right to use, distribute,  and modify it.
////  Therefore,  by  using,  distributing,  or modifying  the  FreeType
////  Project, you indicate that you understand and accept all the terms
////  of this license.
////
////4. Contacts
////-----------
////
////  There are two mailing lists related to FreeType:
////
////    o freetype@nongnu.org
////
////      Discusses general use and applications of FreeType, as well as
////      future and  wanted additions to the  library and distribution.
////      If  you are looking  for support,  start in  this list  if you
////      haven't found anything to help you in the documentation.
////
////    o freetype-devel@nongnu.org
////
////      Discusses bugs,  as well  as engine internals,  design issues,
////      specific licenses, porting, etc.
////
////  Our home page can be found at
////
////    http://www.freetype.org
////
////--- end of FTL.TXT ---


// There is a compiler option for this file, opennurbs_freetype.cpp, that
// adds ./freetype263/include to the list of "system" include paths.
#include "opennurbs_freetype_include.h"

#if defined(ON_COMPILER_MSC)
#pragma ON_PRAGMA_WARNING_PUSH
/*
Suppress 4263 warnings from dwrite.h
Warning	C4263	..: member function does not override any base class virtual member function ... C:\Program Files (x86)\Windows Kits\8.1\Include\um\DWrite.h ...
Warning	C4263	..: member function does not override any base class virtual member function ... C:\Program Files (x86)\Windows Kits\8.1\Include\um\DWrite_1.h ...
Warning	C4263	..: member function does not override any base class virtual member function ... C:\Program Files (x86)\Windows Kits\8.1\Include\um\dwrite_2.h ...
*/
#pragma ON_PRAGMA_WARNING_DISABLE_MSC( 4263 4264 )
#endif

#include FT_OUTLINE_H
#include FT_GLYPH_H
#include FT_MODULE_H

#include "opennurbs_win_dwrite.h"

#if defined(ON_COMPILER_MSC)
#pragma ON_PRAGMA_WARNING_POP
#endif

class ON_FontFileBuffer
{
public:
  ON_FontFileBuffer() = default;

  ~ON_FontFileBuffer()
  {
    Internal_Destroy();
  }

  ON_FontFileBuffer(const ON_FontFileBuffer& src)
  {
    Internal_CopyFrom(src);
  } 

  ON_FontFileBuffer& operator=(const ON_FontFileBuffer& src)
  {
    Internal_CopyFrom(src);
    return *this;
  }

  void* Buffer() const
  {
    return m_buffer;
  }

  void* AllocateBuffer(size_t sizeof_buffer)
  {
    if (sizeof_buffer != m_sizeof_buffer)
    {
      Internal_Destroy();
      if (sizeof_buffer > 0)
      {
        m_buffer = onmalloc(sizeof_buffer);
        if (nullptr != m_buffer)
        {
          m_sizeof_buffer = sizeof_buffer;
        }
      }
    }
    return m_buffer;
  }

  size_t SizeOfBuffer() const
  {
    return m_sizeof_buffer;
  }

  void TransferTo(
    ON_FontFileBuffer& dest
  )
  {
    if (this != &dest)
    {
      dest.Internal_Destroy();
      dest.m_buffer = m_buffer;
      m_buffer = nullptr;
      dest.m_sizeof_buffer = m_sizeof_buffer;
      m_sizeof_buffer = 0;
    }
  }

private:
  size_t m_sizeof_buffer = 0;
  void* m_buffer = nullptr;

private:
  void Internal_CopyFrom(const ON_FontFileBuffer& src)
  {
    AllocateBuffer(src.m_sizeof_buffer);
    if (nullptr != m_buffer)
    {
      memcpy(m_buffer, src.m_buffer, m_sizeof_buffer);
    }
  }

  void Internal_Destroy()
  {
    if (nullptr != m_buffer)
    {
      onfree(m_buffer);
      m_buffer = nullptr;
    }
    m_sizeof_buffer = 0;
  }
};

class ON_FreeTypeFace
{
public:
  ON_FreeTypeFace() = default;
  ~ON_FreeTypeFace();
  FT_Face m_face = nullptr;
  ON_FontFileBuffer m_font_buffer;
private:
  ON_FreeTypeFace(const ON_FreeTypeFace&) = delete;
  ON_FreeTypeFace& operator=(const ON_FreeTypeFace&) = delete;
};

ON_FreeTypeFace::~ON_FreeTypeFace()
{
  //  FT_New_Memory_Face documentation states:
  //    You must not deallocate the memory before calling @FT_Done_Face.
  //    The memory refered to is m_tt_file_buffer.
  if (nullptr != m_face)
  {
    FT_Done_Face(m_face);
    m_face = nullptr;
  }
  m_font_buffer.AllocateBuffer(0);
}

class ON_FreeType
{
public:
  static FT_Library Library();

  static ON_FreeTypeFace* CreateFace(
    const ON_Font& font
  );

  /*
  Description:
    Finds the glyph id for the specified Unicode code point. When a non-zero
    glyph is is returned, the face->charmap is set to the charmap that was used
    to find the glyph. In some cases this is not a Unicode charmap and unicode_code_point
    was internally coverted to a character code appropriate for the returned charmap.
    In principle, the glyph id for a Unicode code point is independent of the charmap.
  Returns:
     0: failure
    >0: font glyph id
  */
  static unsigned int GlyphId(
    FT_Face face,
    ON__UINT32 unicode_code_point
  );

  static const ON_wString FaceFlagsToString(FT_Long face_flags);

  static const ON_wString StyleFlagsToString(FT_Long style_flags);

  static const ON_wString EncodingTypeToString(
    FT_Encoding charmap_encoding
  );

  static const ON_wString CharmapPlatformEncodingDescription(
    const FT_CharMap cmap
  );

  static bool UseUnicodeAsAppleRomanCharCode(
    FT_Face face
  );

  static bool IsDamagedCharMap(
    FT_CharMap cmap
  );

private:
  static FT_MemoryRec_ m_memory_rec;

  static FT_Library m_library;

private:
#if defined(ON_RUNTIME_WIN)
  static ON_FreeTypeFace* Internal_CreateFaceFromWindowsFont(
    const LOGFONT* logfont
  );
#endif
#if defined (ON_RUNTIME_APPLE_CORE_TEXT_AVAILABLE)
  static ON_FreeTypeFace* Internal_CreateFaceFromAppleFont(CTFontRef);
#endif
};

FT_MemoryRec_ ON_FreeType::m_memory_rec;
FT_Library ON_FreeType::m_library = nullptr;

static void* ON_Internal_FT_Alloc_Func(
  FT_Memory  memory,
  long size)
{
  void* ptr = onmalloc(size);
  return ptr;
}

void ON_Internal_FT_Free_Func(
  FT_Memory  memory,
  void* block
)
{
  onfree(block);
}

void* ON_InternalFT_Realloc_Func(
  FT_Memory  memory,
  long cur_size,
  long new_size,
  void* block
)
{
  void* ptr = onrealloc(block, new_size);
  return ptr;
}

FT_Library ON_FreeType::Library()
{
  if (nullptr == ON_FreeType::m_library)
  {
#if 1
    // The only reason a custom memory allocator is used
    // is so memory allocated by freetype is not flagged
    // as a leak because it is used in the cached fonts
    // ON_Font::ManagedFont(). These are created as needed
    // one per face and never deleted.
    memset(&ON_FreeType::m_memory_rec, 0, sizeof(ON_FreeType::m_memory_rec));
    ON_FreeType::m_memory_rec.user = nullptr;
    ON_FreeType::m_memory_rec.alloc = ON_Internal_FT_Alloc_Func;
    ON_FreeType::m_memory_rec.free = ON_Internal_FT_Free_Func;
    ON_FreeType::m_memory_rec.realloc = ON_InternalFT_Realloc_Func;

    FT_Library library = nullptr;
    int rc = FT_New_Library(&ON_FreeType::m_memory_rec, &library);
    if ( 0 == rc && nullptr != library )
      FT_Add_Default_Modules( library );
#else
    // Works fin except freetype cached information is flagged as a memory leak
    // int rc = FT_Init_FreeType(&library);
#endif

    if (0 != rc || nullptr == library)
    {
      ON_ERROR("FreeType FT_Init_FreeType() failed.");
    }
    else
    {
      ON_FreeType::m_library = library;
    }
  }
  return ON_FreeType::m_library;
}

bool ON_FreeType::IsDamagedCharMap(
  FT_CharMap cmap
)
{
  if (nullptr == cmap || nullptr == cmap->face)
    return true;
  
  bool rc = false;
  
  switch (cmap->encoding)
  {
  case FT_ENCODING_APPLE_ROMAN:
    rc = ON_FreeType::UseUnicodeAsAppleRomanCharCode(cmap->face);
    break;
  }

  return rc;
}

#define ON__FLAG_TO_STR(flag,i,v,s) do {if ( flag == (i & flag) ) {if (s.IsNotEmpty()) s += L", "; s += v; }} while(false)

const ON_wString ON_FreeType::FaceFlagsToString(FT_Long face_flags)
{
  ON_wString s;
  ON__FLAG_TO_STR(FT_FACE_FLAG_SCALABLE, face_flags, L"SCALABLE", s);
  ON__FLAG_TO_STR(FT_FACE_FLAG_FIXED_SIZES, face_flags, L"FIXED_SIZES", s);
  ON__FLAG_TO_STR(FT_FACE_FLAG_FIXED_WIDTH, face_flags, L"FIXED_WIDTH", s);
  ON__FLAG_TO_STR(FT_FACE_FLAG_SFNT, face_flags, L"SFNT", s);
  ON__FLAG_TO_STR(FT_FACE_FLAG_HORIZONTAL, face_flags, L"HORIZONTAL", s);
  ON__FLAG_TO_STR(FT_FACE_FLAG_VERTICAL, face_flags, L"VERTICAL", s);
  ON__FLAG_TO_STR(FT_FACE_FLAG_KERNING, face_flags, L"KERNING", s);
  ON__FLAG_TO_STR(FT_FACE_FLAG_FAST_GLYPHS, face_flags, L"FAST_GLYPHS", s);
  ON__FLAG_TO_STR(FT_FACE_FLAG_MULTIPLE_MASTERS, face_flags, L"MULTIPLE_MASTERS", s);
  ON__FLAG_TO_STR(FT_FACE_FLAG_GLYPH_NAMES, face_flags, L"GLYPH_NAMES", s);
  ON__FLAG_TO_STR(FT_FACE_FLAG_EXTERNAL_STREAM, face_flags, L"EXTERNAL_STREAM", s);
  ON__FLAG_TO_STR(FT_FACE_FLAG_HINTER, face_flags, L"FLAG_HINTER", s);
  ON__FLAG_TO_STR(FT_FACE_FLAG_CID_KEYED, face_flags, L"CID_KEYED", s);
  ON__FLAG_TO_STR(FT_FACE_FLAG_TRICKY, face_flags, L"FLAG_TRICKY", s);
  ON__FLAG_TO_STR(FT_FACE_FLAG_COLOR, face_flags, L"FLAG_COLOR", s);
  return s;
}

const ON_wString ON_FreeType::StyleFlagsToString(FT_Long style_flags)
{
  ON_wString s;
  ON__FLAG_TO_STR(FT_STYLE_FLAG_BOLD, style_flags, L"BOLD", s);
  ON__FLAG_TO_STR(FT_STYLE_FLAG_ITALIC, style_flags, L"ITALIC", s);
  return s;
}


bool ON_FreeType::UseUnicodeAsAppleRomanCharCode(FT_Face face)
{

  // It's not clear if the bug is in these font files or Windows or Freetype or
  // the way we are selecting maps, but the fonts listed below need this hack to
  // find the correct glyph and other fonts don't.
  //
  // The mighty internet contains mentions of other people having trouble with these fonts as well.
  //
  // It appears that for these fonts, passing a Unicode code point value
  // to the cmap[] idenfied as "FT_ENCODING_APPLE_ROMAN" will get the correct
  // glyph id. 
  //
  // I have verified that the way opennurbs handles FT_ENCODING_APPLE_ROMAN charmaps
  // and the funciton ON_MapUnicodeToAppleRoman() works correctly with all the
  // TTF fonts shipped with Windows 10 pro.
  //
  if (
    nullptr != face
    && 1 == face->num_faces
    && 3 == face->num_charmaps
    && nullptr != face->charmaps[0]
    && nullptr != face->charmaps[1]
    && nullptr != face->charmaps[2]
    && 0 == face->charmaps[0]->platform_id && 0 == face->charmaps[0]->encoding_id
    && 1 == face->charmaps[1]->platform_id && 0 == face->charmaps[1]->encoding_id
    && 3 == face->charmaps[2]->platform_id && 0 == face->charmaps[2]->encoding_id
    )
  {
    // May 2017 Dale Lear:
    //
    // Fonts we've found where FT_ENCODING_APPLE_ROMAN charmap does not work correctly
    // with Apple Roman char codes.
    //
    // Linguist's Software CityBlueprint 2.0 generated with Altsys Fontographer 4.1 9/17/96
    // Linguist's Software CountryBlueprint 2.0 generated with Altsys Fontographer 4.1 9/17/96
    // Linguist's Software Romantic 2.0 generated with Altsys Fontographer 4.1 9/17/96
    // Linguist's Software SansSerif 2.0 generated with Altsys Fontographer 4.1 9/17/96
    // Linguist's Software Technic 2.0 generated with Altsys Fontographer 4.1 9/17/96
    // 
    // So far, all styles with these face names have the buggy cmap[].
    if (ON_String::EqualOrdinal(face->family_name, "CityBlueprint", false))
      return true;
    if (ON_String::EqualOrdinal(face->family_name, "CountryBlueprint", false))
      return true;
    if (ON_String::EqualOrdinal(face->family_name, "Romantic", false))
      return true;
    if (ON_String::EqualOrdinal(face->family_name, "SansSerif", false))
      return true;
    if (ON_String::EqualOrdinal(face->family_name, "Technic", false))
      return true;
  }
  return false;
}

const ON_wString ON_FreeType::EncodingTypeToString( FT_Encoding charmap_encoding )
{
  ON_wString e;
  
  switch (charmap_encoding)
  {
  case FT_ENCODING_NONE:           e = L"FT_ENCODING_NONE"; break;
  case FT_ENCODING_UNICODE:        e = L"FT_ENCODING_UNICODE"; break;
  case FT_ENCODING_MS_SYMBOL:      e = L"FT_ENCODING_MS_SYMBOL"; break;
  case FT_ENCODING_ADOBE_LATIN_1:  e = L"FT_ENCODING_ADOBE_LATIN_1"; break;
  case FT_ENCODING_OLD_LATIN_2:    e = L"FT_ENCODING_OLD_LATIN_2"; break;
  case FT_ENCODING_SJIS:           e = L"FT_ENCODING_SJIS"; break;
  case FT_ENCODING_GB2312:         e = L"FT_ENCODING_GB2312"; break;
  case FT_ENCODING_BIG5:           e = L"FT_ENCODING_BIG5"; break;
  case FT_ENCODING_WANSUNG:        e = L"FT_ENCODING_WANSUNG"; break;
  case FT_ENCODING_JOHAB:          e = L"FT_ENCODING_JOHAB"; break;
  case FT_ENCODING_ADOBE_STANDARD: e = L"FT_ENCODING_ADOBE_STANDARD"; break;
  case FT_ENCODING_ADOBE_EXPERT:   e = L"FT_ENCODING_ADOBE_EXPERT"; break;
  case FT_ENCODING_ADOBE_CUSTOM:   e = L"FT_ENCODING_ADOBE_CUSTOM"; break;
  case FT_ENCODING_APPLE_ROMAN:    e = L"FT_ENCODING_APPLE_ROMAN"; break;
  default: 
    e = ON_wString::FormatToString(
      L"((FT_Encoding)%u)",
      static_cast<unsigned int>(charmap_encoding)
    );
    break;
  }
  
  return e;
}

const ON_wString ON_FreeType::CharmapPlatformEncodingDescription( const FT_CharMap cmap )
{
  // Reference
  // https://www.microsoft.com/typography/otspec/name.htm#enc3

  ON_wString platform;
  ON_wString encoding;
  switch (cmap->platform_id)
  {
  case 0: 
    platform = L"Unicode"; 
    switch (cmap->encoding_id)
    {
    case 0: encoding = L"Unicode 1.0 semantics [deprecated]"; break;
    case 1: encoding = L"Unicode 1.1 semantics [deprecated]"; break;
    case 2: encoding = L"ISO/IEC 10646 semantics [deprecated]"; break;
    case 3: encoding = L"Unicode 2.0+ semantics BMP only"; break;
    case 4: encoding = L"Unicode 2.0+ semantics"; break;
    case 5: encoding = L"Unicode Variation Sequences"; break;
    case 6: encoding = L"Unicode full repertoire"; break;
    }
    break;

  case 1: 
    platform = L"Apple Script Manager";
    switch (cmap->encoding_id)
    {
    case  0: encoding = L"Roman"; break;
    case  1: encoding = L"Japanese"; break;
    case  2: encoding = L"Chinese (Traditional)"; break;
    case  3: encoding = L"Korean"; break;
    case  4: encoding = L"Arabic"; break;
    case  5: encoding = L"Hebrew"; break;
    case  6: encoding = L"Greek"; break;
    case  7: encoding = L"Russian"; break;
    case  8: encoding = L"RSymbol"; break;
    case  9: encoding = L"Devanagari"; break;
    case 10: encoding = L"Gurmukhi"; break;
    case 11: encoding = L"Gujarati"; break;
    case 12: encoding = L"Oriya"; break;
    case 13: encoding = L"Bengali"; break;
    case 14: encoding = L"Tamil"; break;
    case 15: encoding = L"Telugu"; break;
    case 16: encoding = L"Kannada"; break;
    case 17: encoding = L"Malayalam"; break;
    case 18: encoding = L"Sinhalese"; break;
    case 19: encoding = L"Burmese"; break;
    case 20: encoding = L"Khmer"; break;
    case 21: encoding = L"Thai"; break;
    case 22: encoding = L"Laotian"; break;
    case 23: encoding = L"Georgian"; break;
    case 24: encoding = L"Armenian"; break;
    case 25: encoding = L"Chinese (Simplified)"; break;
    case 26: encoding = L"Tibetan"; break;
    case 27: encoding = L"Mongolian"; break;
    case 28: encoding = L"Geez"; break;
    case 29: encoding = L"Slavic"; break;
    case 30: encoding = L"Vietnamese"; break;
    case 31: encoding = L"Sindhi"; break;
    case 32: encoding = L"Uninterpreted"; break;
    }
    break;

  case 2:
    platform = L"ISO [deprecated]";
    switch (cmap->encoding_id)
    {
    case 0: encoding = L"7-bit ASCII"; break;
    case 1: encoding = L"ISO 10646"; break;
    case 2: encoding = L"ISO 8859-1"; break;
    }
    break;

  case 3:
    platform = L"Windows"; 
    switch (cmap->encoding_id)
    {
    case  0: encoding = L"Symbol"; break;
    case  1: encoding = L"Unicode BMP UCS-2"; break;
    case  2: encoding = L"ShiftJIS"; break;
    case  3: encoding = L"PRC"; break;
    case  4: encoding = L"Big5"; break;
    case  5: encoding = L"Wansung"; break;
    case  6: encoding = L"Johab"; break;
    case 10: encoding = L"Unicode UCS-4"; break;
    }
    break;
  }


  const ON_wString e = ON_FreeType::EncodingTypeToString(cmap->encoding);

  ON_wString s = ON_wString::FormatToString(
    L"%ls %d-%d",
    static_cast<const wchar_t*>(e),
    cmap->platform_id,
    cmap->encoding_id
  );

  if (platform.IsNotEmpty())
  {
    if (encoding.IsEmpty())
      encoding = L"unknown";
    s += ON_wString::FormatToString(
      L" (%ls %ls)",
      static_cast<const wchar_t*>(platform),
      static_cast<const wchar_t*>(encoding)
    );
  }

  return s;
}

bool ON_FontGlyph::TestFreeTypeFaceCharMaps(
  ON_TextLog* text_log
) const
{
  // In order for false to be returned, charmaps[] have to exist and 
  // an explicit error has to be detected. Otherwise, true is returned.
  const ON_Font* font = Font();
  if (nullptr == font)
  {
    if (text_log)
      text_log->Print("Font() = nullptr. Nothing to test.\n");
    return true; // nothing to test.
  }

  if (false == font->IsManagedFont())
  {
    // this "should" never happen.
    if (text_log)
      text_log->Print("Font().IsManagedFont() = false. Nothing to test.\n");
    return true; // nothing to test.
  }

  const ON__UINT32 unicode_code_point = CodePoint();
  if (false == ON_IsValidUnicodeCodePoint(unicode_code_point))
  {
    if (text_log)
      text_log->Print("CodePoint() is not valid. Nothing to test.\n");
    return true; // nothing to test.
  }

  FT_Face face = reinterpret_cast<FT_Face>(ON_Font::FreeTypeFace(font));
  if (nullptr == face)
  {
    if (text_log)
      text_log->Print("Face is nullptr. Nothing to test.\n");
    return true; // nothing to test.
  }

  const unsigned int glyph_index = FontGlyphIndex();
  if ( 0 == glyph_index )
  {
    if (text_log)
      text_log->Print("FontGlyphIndex is 0. Nothing to test.\n");
    return true; // nothing to test.
  }

  // Save current face charmap state
  FT_CharMap charmap0 = face->charmap;

  bool rc = true;
  for (int charmap_index = 0; charmap_index < face->num_charmaps; charmap_index++)
  {
    FT_CharMap charmap = face->charmaps[charmap_index];
    if (nullptr == charmap)
      continue;


    bool bHaveCharMap = false;
    bool bHaveCharCode = false;
    bool bBuggyMap = false;
    bool bUnicode = false;
    unsigned int char_code = 0xFFFFFFFF;
    unsigned int gid = 0;

    const ON_wString e = ON_FreeType::CharmapPlatformEncodingDescription(charmap);

    switch (charmap->encoding)
    {
    case FT_ENCODING_UNICODE:
      char_code = unicode_code_point;
      bHaveCharCode = true;
      bUnicode = true;
      break;

    case FT_ENCODING_APPLE_ROMAN:
      bBuggyMap = ON_FreeType::UseUnicodeAsAppleRomanCharCode(face);
      if ( bBuggyMap )
      {
        bHaveCharCode = unicode_code_point <= 0xFF;
        if (bHaveCharCode)
        {
          bUnicode = true;
          char_code = unicode_code_point;
        }
      }
      else
      {
        // Microsoft code page 10000 = Apple Roman encoding
        char_code = ON_MapUnicodeToMSSBCP(10000, unicode_code_point);
        bHaveCharCode = (char_code <= 0xFF);
      }
      break;

    case FT_ENCODING_MS_SYMBOL:
      bHaveCharCode = true;
      char_code = unicode_code_point;
      break;

    default:
      break;
    }

    if (bHaveCharCode)
    {
      bHaveCharMap 
        = FT_Err_Ok == FT_Set_Charmap(face, charmap)
        && charmap == face->charmap;
      if (bHaveCharMap)
      {
        gid = FT_Get_Char_Index(face, char_code);
        if (glyph_index != gid)
          rc = false;
      }
      else
        rc = false;
    }

    if (text_log)
    {
      const wchar_t* damaged = ( bBuggyMap || ON_FreeType::IsDamagedCharMap(charmap) )? L"DAMAGED " : L"";
      ON_wString s = ON_wString::FormatToString(
        L"%lscmap[%d] %ls",
        damaged,
        charmap_index,
        static_cast<const wchar_t*>(e)
      );

      s += ON_wString::FormatToString(L" U+%04X", unicode_code_point);
      if (false == bHaveCharCode)
        s += L" (no char code)";
      if (false == bUnicode || bBuggyMap || char_code != unicode_code_point)
        s += ON_wString::FormatToString(L" -> char code 0x%X (%u)", char_code, char_code);

      if (false == bHaveCharMap)
      {
        s += L" ERROR(cannot use cmap)";
      }
      else if (0 == gid)
      {
        s += ON_wString::FormatToString(
          L" no glpyh",
          char_code
        );
      }
      else
      {
        s += ON_wString::FormatToString(
          L" -> glyph id %u",
          gid
        );
        if (glyph_index != gid)
        {
          s += ON_wString::FormatToString(L"ERROR(expected glyph index %u)",glyph_index);
        }                
      }
      text_log->Print(L"%ls\n", static_cast<const wchar_t*>(s));
    }
  }

  // restore face charmap state
  FT_Set_Charmap(face, charmap0);
  
  return rc;
}

unsigned int ON_FreeType::GlyphId(
    FT_Face face,
    ON__UINT32 unicode_code_point
  )
{
  if (nullptr == face)
    return 0;

  const FT_CharMap charmap0 = face->charmap;
  if (nullptr == charmap0 || FT_ENCODING_UNICODE != charmap0->encoding)
  {
    if (FT_Err_Ok != FT_Select_Charmap(face, FT_ENCODING_UNICODE))
      FT_Set_Charmap(face, charmap0);
  }

  FT_CharMap charmap1 = face->charmap;
  if (nullptr != charmap1 && FT_ENCODING_UNICODE == charmap1->encoding)
  {
    unsigned int glyph_id = FT_Get_Char_Index(face, unicode_code_point);
    if (0 != glyph_id)
    {
      // commonly happens for well designed modern fonts.
      return glyph_id;
    }
  }
  else
  {
    charmap1 = nullptr;
  }

  // May 2017 Dale Lear
  //
  // In some fonts, there are multiple FT_ENCODING_UNICODE charmaps
  // and they can map different unicode code points. These typically are
  // Windows UCS-2 and Windows UCS-4 cmaps. UCS-2 and UCS-4 values subsets of Unicode.
  //
  // In fonts like CityBlueprint and CountryBlueprint (which many customers use), there 
  // is no FT_ENCODING_UNICODE charmap but there is a viable FT_ENCODING_APPLE_ROMAN charmap.
  //
  // As we discover fonts that customers use, we will add support for their charmaps.
  //
  // TrueType platform_id and encoding_id. The encoding id is platform specific.
  // platform_id-encoding_id 
  //
  //  0-* "Apple *code" - encoding id varies 
  //
  //  1-0 Apple Roman (256 codes - see ON_MapAppleRomanToUnicode())
  //  1-* Apple (encoding id = script manager)
  //
  //  2-* ISO (encoding id = ISO encoding)
  //
  //  3-0 Windows Symbol
  //  3-1 Wnidows UCS-2 (subset of Unicode)
  //  3-2 Windows ShiftJIS
  //  3-3 Windows Big5
  //  3-4 WIndows PRC
  //  3-5 Windows Wansung
  //  3-6 Windows Johab
  //  3-10 Wnidows UCS-4 (subset of Unicode)
  //
  //  4-* Custom
  //
  // http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-chapter08
  // 
  const FT_Encoding encoding_pass[]
  {
    FT_ENCODING_UNICODE,
    FT_ENCODING_APPLE_ROMAN,
    FT_ENCODING_NONE // FT_ENCODING_NONE must terminate the array
  };

  const size_t pass_count = sizeof(encoding_pass) / sizeof(encoding_pass[0]);

  for (size_t pass = 0; pass < pass_count; pass++)
  {
    if (pass+1 >= pass_count && unicode_code_point >= 0x80)
    {
      // As a last gasp pass, when the unicode_code_point < 0x80, we try every charmap
      // because using the "ASCII encoding" for those code points was farily common
      // in old fonts and the charmap identifion may be incorrect.
      break;
    }

    // Often, ft_face has multiple FT_ENCODING_UNICODE charmaps and
    // sometimes FT_Select_Charmap() sometimes selects the wrong one
    // when we want a glyph for a specific unicode code point.
    const FT_Encoding e = encoding_pass[pass];
    for ( int charmap_index = 0; charmap_index < face->num_charmaps; charmap_index++)
    {
      const FT_CharMap charmap = face->charmaps[charmap_index];
      if (nullptr == charmap)
        continue;

      if (charmap1 == charmap)
        continue; // charmap1 was already tested.

      if (FT_ENCODING_NONE != e)
      {
        if ( e != charmap->encoding)
         continue; // wrong encoding for this pass
      }
      else
      {
        if (FT_ENCODING_UNICODE == charmap->encoding)
          continue; // already tested
      }

      if (FT_Err_Ok != FT_Set_Charmap(face, charmap))
        continue;
      if (charmap != face->charmap)
        continue;

      unsigned int charcode = 0xFFFFFFFF;
      switch (e)
      {
      case FT_ENCODING_APPLE_ROMAN:
        // Required to get CityBlueprint and CountryBlueprint fonts to work correctly.
        if (ON_FreeType::UseUnicodeAsAppleRomanCharCode(face))
        {
          // Buggy font.
          // The FT_ENCODING_APPLE_ROMAN encoding in these fonts is really a unicode encoding.
          charcode = unicode_code_point;
        }
        else
        {
          // Microsoft code page 10000 = Apple Roman
          // Single byte encoding
          charcode = ON_MapUnicodeToMSSBCP(10000, unicode_code_point);
          if (charcode > 0xFF)
            continue; // no mapping from Unicode to Apple Roman
        }
        break;

      case FT_ENCODING_UNICODE:
        charcode = unicode_code_point;
        break;

      default:
        // This is risky but might work when unicode_code_point < 0x80
        charcode = unicode_code_point;
        break;
      }

      // see if the glyph is in this char map
      unsigned int glyph_id = FT_Get_Char_Index(face, charcode);
      if (0 == glyph_id)
        continue;

      return glyph_id;
    }
  }

  // No glpyh for this unicode code point is available.
  FT_Set_Charmap(face, charmap0);

  return 0;
}

#if defined(ON_RUNTIME_WIN)

static bool Internal_CreateFontBufferFromDirectWrite(
  IDWriteFont* dwriteFont,
  ON_FontFileBuffer& font_buffer
)
{
  font_buffer.AllocateBuffer(0);

  if (nullptr == dwriteFont)
    return false;

  Microsoft::WRL::ComPtr<IDWriteFontFace> dwriteFontFace = nullptr;
  HRESULT hr = dwriteFont->CreateFontFace(&dwriteFontFace);
  if (FAILED(hr))
    return false;
  if (nullptr == dwriteFontFace || nullptr == dwriteFontFace.Get() )
    return false;

  UINT32 numfiles = 0;
  hr = dwriteFontFace->GetFiles(&numfiles, nullptr);
  if (FAILED(hr))
    return false;
  if (numfiles <= 0)
    return false;

  if (numfiles > 1)
  {
    // The docs state that this function should be called twice; first to obtain
    // the number of files. In the context of FreeType, I'm not sure what to do
    // with multiple files.
    ON_WARNING("Multiple font files.");
    numfiles = 1;
  }
  
  Microsoft::WRL::ComPtr<IDWriteFontFile> dwriteFontFile = nullptr;
  hr = dwriteFontFace->GetFiles(&numfiles, &dwriteFontFile);
  if (FAILED(hr))
    return false;
  if (nullptr == dwriteFontFile || nullptr == dwriteFontFile.Get())
    return false;

  const void* reference_key = nullptr;
  UINT32 reference_key_size = 0;
  hr = dwriteFontFile->GetReferenceKey(&reference_key, &reference_key_size);
  if (FAILED(hr))
    return false;

  Microsoft::WRL::ComPtr<IDWriteFontFileLoader> dwriteFontFileLoader = nullptr;
  hr = dwriteFontFile->GetLoader(&dwriteFontFileLoader);
  if (FAILED(hr))
    return false;
  if (nullptr == dwriteFontFileLoader || nullptr == dwriteFontFileLoader.Get() )
    return false;

  Microsoft::WRL::ComPtr<IDWriteFontFileStream> dwriteFontFileStream = nullptr;
  hr = dwriteFontFileLoader->CreateStreamFromKey(reference_key, reference_key_size, &dwriteFontFileStream);
  if (FAILED(hr))
    return false;
  if (nullptr == dwriteFontFileStream || nullptr == dwriteFontFileStream.Get() )
    return false;

  UINT64 filesize = 0;
  hr = dwriteFontFileStream->GetFileSize(&filesize);
  if (FAILED(hr))
    return false;
  if (filesize <= 0)
    return false;

  const void *fragstart = nullptr;
  void *fragcontext = nullptr;
  hr = dwriteFontFileStream->ReadFileFragment(&fragstart, 0, filesize, &fragcontext);
  if (FAILED(hr))
    return false;

  if (nullptr != fragstart)
  {
    void* buffer = font_buffer.AllocateBuffer((size_t)filesize);
    if (nullptr != buffer && font_buffer.SizeOfBuffer() >= (size_t)filesize)
      memcpy(buffer, fragstart, font_buffer.SizeOfBuffer());
  }

  dwriteFontFileStream->ReleaseFileFragment(fragcontext);

  return (font_buffer.SizeOfBuffer() > 0);
}

static bool Internal_CreateFontBufferFromDirectWrite(
  const LOGFONT& logfont,
  ON_FontFileBuffer& font_buffer
)
{
  font_buffer.AllocateBuffer(0);

  if (0 == logfont.lfFaceName[0])
    return false;

  Microsoft::WRL::ComPtr<IDWriteFactory> dwriteFactory = nullptr;
  HRESULT hr = DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), &dwriteFactory);
  if (FAILED(hr))
    return false;
  if (nullptr == dwriteFactory || nullptr == dwriteFactory.Get())
    return false;

  //fonts on this computer
  Microsoft::WRL::ComPtr<IDWriteFontCollection> dwriteFontCollection = nullptr;
  hr = dwriteFactory->GetSystemFontCollection(&dwriteFontCollection, true);
  if (FAILED(hr))
    return false;
  if (nullptr == dwriteFontCollection || nullptr == dwriteFontCollection.Get())
    return false;

  Microsoft::WRL::ComPtr< IDWriteGdiInterop > dwriteGdiInterop = nullptr;
  hr = dwriteFactory->GetGdiInterop(&dwriteGdiInterop);
  if (FAILED(hr))
    return false;
  if (nullptr == dwriteGdiInterop || nullptr == dwriteGdiInterop.Get())
    return false;

  LOGFONT cleanLogFont = logfont;

  cleanLogFont.lfHeight = 0;
  cleanLogFont.lfWidth = 0;
  cleanLogFont.lfEscapement = 0;
  cleanLogFont.lfOrientation = 0;
  if (cleanLogFont.lfWeight < 1 || cleanLogFont.lfWeight > 999)
    cleanLogFont.lfWeight = 400;
  if (0 != cleanLogFont.lfItalic) 
    cleanLogFont.lfItalic = 1;
  if (0 != cleanLogFont.lfUnderline) 
    cleanLogFont.lfUnderline = 1;
  if (0 != cleanLogFont.lfStrikeOut) 
    cleanLogFont.lfStrikeOut = 1;
  if (ON_Font::WindowsConstants::logfont_symbol_charset != cleanLogFont.lfCharSet)
    cleanLogFont.lfCharSet = ON_Font::WindowsConstants::logfont_default_charset;
  cleanLogFont.lfOutPrecision = ON_Font::WindowsConstants::logfont_out_precis;
  cleanLogFont.lfClipPrecision = 0;
  cleanLogFont.lfQuality = ON_Font::WindowsConstants::logfont_quality;
  cleanLogFont.lfPitchAndFamily = ON_Font::WindowsConstants::logfont_pitch_and_family;

  Microsoft::WRL::ComPtr<IDWriteFont> dwriteFont = nullptr;
  hr = dwriteGdiInterop->CreateFontFromLOGFONT(&cleanLogFont, &dwriteFont);
  if (FAILED(hr))
    return false;
  if (nullptr == dwriteFont || nullptr == dwriteFont.Get())
    return false;

  return Internal_CreateFontBufferFromDirectWrite(dwriteFont.Get(), font_buffer);
}

static bool Internal_CreateFontBufferFromGDI(
  const LOGFONT& logfont,
  ON_FontFileBuffer& font_buffer
)
{
  font_buffer.AllocateBuffer(0);

  HDC font_hdc = nullptr;
  HFONT hfont = nullptr;
  HGDIOBJ hfont_original = nullptr;
  bool rc = false;
  for (;;)
  {
    hfont = ::CreateFontIndirect(&logfont);
    if (0 == hfont)
    {
      ON_ERROR("CreateFontIndirect failed.");
      break;
    }

    font_hdc = ON_Font::CreateWindowsLogfontDeviceContext();
    if (nullptr == font_hdc)
    {
      ON_ERROR("ON_Font::CreateWindowsLogfontDeviceContext()(nullptr) failed.");
      break;
    }

    hfont_original = ::SelectObject(font_hdc, hfont);
    if (nullptr == hfont_original)
    {
      ON_ERROR("SelectObject(hdc, hfont) failed.");
      break;
    }

    //const DWORD dwTable_TrueTypeCollection = 0x66637474;
    DWORD  dwTable = 0;
    DWORD  dwOffset = 0;
    const DWORD buffer_capacity = ::GetFontData(font_hdc, dwTable, dwOffset, nullptr, 0);
    if (buffer_capacity <= 0)
    {
      ON_ERROR("GetFontData(...,nullptr,0) failed.");
      break;
    }

    void* buffer = font_buffer.AllocateBuffer(buffer_capacity);
    if (nullptr == buffer)
    {
      ON_ERROR("onmalloc(buffer_capacity) failed.");
      break;
    }    
    memset(buffer, 0, buffer_capacity);

    const DWORD buffer_size = ::GetFontData(font_hdc, dwTable, dwOffset, buffer, buffer_capacity);
    if ( buffer_size != buffer_capacity )
    {
      ON_ERROR("GetFontData(...,nullptr,0) failed.");
      break;
    }

    rc = true;
    break;
  }

  if (false == rc)
  {
    font_buffer.AllocateBuffer(0);
  }

  if (nullptr != font_hdc)
  {
    if (nullptr != hfont_original)
      ::SelectObject(font_hdc, hfont_original);
    ON_Font::DeleteWindowsLogfontDeviceContext(font_hdc);
  }

  if (nullptr != hfont)
  {
    ::DeleteObject(hfont);
  }

  return (font_buffer.SizeOfBuffer() > 0);
}

ON_FreeTypeFace* ON_FreeType::Internal_CreateFaceFromWindowsFont(
  const LOGFONT* logfont
)
{
  if (nullptr == logfont)
    return nullptr;

  FT_Library freetype_library = ON_FreeType::Library();
  if (nullptr == freetype_library)
    return nullptr;
  for (int pass = 1; pass <= 2; pass++)
  {
    ON_FontFileBuffer font_buffer;

    // May 2015 Dale Lear
    //  The DirectWrite approach yields better results in some cases.
    //  For example, the Yu Gothic in Windows 10
    //    The font_buffer created by DirectWrite results in an FT_Face
    //    with 3 charmaps (2 Uniocde) and the font_buffer created by 
    //    GDI has 0 charmaps which means getting a glyph from a code point
    //    is not possible.
    const bool bHaveBuffer
      = (1 == pass)
      ? Internal_CreateFontBufferFromDirectWrite(*logfont, font_buffer)
      : Internal_CreateFontBufferFromGDI(*logfont, font_buffer);

    if (false == bHaveBuffer)
      continue;
    if (font_buffer.SizeOfBuffer() <= 0)
      continue;
    if (nullptr == font_buffer.Buffer())
      continue;
    
    int font_face_index = 0;
    FT_Face face = nullptr;
    FT_Error rc = FT_New_Memory_Face(
      freetype_library,
      reinterpret_cast<const FT_Byte*>(font_buffer.Buffer()),
      (FT_Long)font_buffer.SizeOfBuffer(),
      font_face_index,
      &face
    );

    if (nullptr == face)
      continue;

    if (FT_Err_Ok != rc)
    {
      FT_Done_Face(face);
      continue;
    }

    ON_FreeTypeFace* f = new ON_FreeTypeFace();

    f->m_face = face;
    font_buffer.TransferTo(f->m_font_buffer);

    return f;
  }

  return nullptr;
}

#endif


#if defined (ON_RUNTIME_APPLE_CORE_TEXT_AVAILABLE)

ON_FreeTypeFace* ON_FreeType::Internal_CreateFaceFromAppleFont (CTFontRef fontRef)
{
  if (nullptr == fontRef)
    return nullptr;
  
  // determine file path for CTFont
  CFURLRef fontURLRef = (CFURLRef) CTFontCopyAttribute (fontRef, kCTFontURLAttribute);
  
  NSURL* fontURL = (NSURL*) CFBridgingRelease (fontURLRef);
  const char* path = fontURL.path.UTF8String;
  
  // Search all the faces in this font file for a face that matches the NSFont family and style
  FT_Face ftFace;
  FT_Error err = FT_New_Face (ON_FreeType::Library(), path, 0, &ftFace);    // get first face
  if (err)
    return nullptr;     // that didn't work
  
  const int numFaces = (int)ftFace->num_faces; // determine number of faces in the font file

  ON_FreeTypeFace* rc = new ON_FreeTypeFace();
  if (numFaces <= 1)
  {
    rc->m_face = ftFace;
    return rc;       // only one face, so this must be the right one
  }
  
  int faceIndex = 0;
  for (;;)
  {
    const ON_wString appleFamilyName = ON_Font::AppleCTFontFamilyName(fontRef);
    const ON_wString appleStyleName = ON_Font::AppleCTFontFaceName(fontRef);
    const ON_wString ftFamilyName(ftFace->family_name);
    const ON_wString ftStyleName(ftFace->style_name);

    if (
        ON_wString::EqualOrdinal(appleFamilyName, ftFamilyName, true)
        && ON_wString::EqualOrdinal(appleStyleName, ftStyleName, true)
        )
    {
      rc->m_face = ftFace;
      return rc;
    }
    
    // No match.  Step to next face.
    FT_Done_Face (ftFace);
    
    FT_Error err = FT_New_Face (ON_FreeType::Library(), path, ++faceIndex, &ftFace);
    if (ftFace == nullptr || err || faceIndex >= numFaces) {
      // Ran out of faces to inspect or FT_New_Face returned an error.
      FT_Done_Face (ftFace);
      break;
    }
  }
  
  // When no match found, use first face in font file as the default face.
  FT_New_Face (ON_FreeType::Library(), path, 0, &ftFace);    // get first face

  rc->m_face = ftFace;
  return rc;
}

#endif    // ON_RUNTIME_APPLE


ON_FreeTypeFace* ON_FreeType::CreateFace(
  const ON_Font& font
)
{
  ON_FreeTypeFace* f = nullptr;

  if (false == font.IsManagedFont())
  {
    // Managed fonts have valid settings.
    return nullptr;
  }

#if defined(ON_RUNTIME_WIN)
  LOGFONT logfont = font.WindowsLogFont(0,nullptr);
  f = ON_FreeType::Internal_CreateFaceFromWindowsFont(&logfont);

#elif defined (ON_RUNTIME_APPLE_OBJECTIVE_C_AVAILABLE)
  f = ON_FreeType::Internal_CreateFaceFromAppleFont(font.AppleCTFont());
#endif

  // Create empty holder so this function doesn't repeatedly
  // try to load the freetype face.
  if (nullptr == f)
    f = new ON_FreeTypeFace();

  return f;
}

ON__UINT_PTR ON_Font::FreeTypeFace(
  const ON_Font* font
)
{
  if (nullptr == font)
    return 0;
  const ON_Font* managed_font = font->ManagedFont();
  if (nullptr == managed_font)
    return 0;

  if (0 == managed_font->m_free_type_face)
  {
    managed_font->m_free_type_face = ON_FreeType::CreateFace(*managed_font);
  }

  FT_Face ft_face
    = (nullptr != managed_font->m_free_type_face)
    ? managed_font->m_free_type_face->m_face
    : nullptr;

  return (ON__UINT_PTR)ft_face;
}

void ON_Font::DestroyFreeTypeFace(
  const ON_Font* font
)
{
  if (nullptr != font && nullptr != font->m_free_type_face)
  {
    if (font->IsManagedFont())
      delete font->m_free_type_face;
    font->m_free_type_face = nullptr;
  }
}

const ON__UINT_PTR ON_FontGlyph::FreeTypeFace() const
{
  return
    (nullptr == m_managed_font)
    ? 0
    : ON_Font::FreeTypeFace(m_managed_font);
}

unsigned int ON_FreeTypeGetFontUnitsPerM(
  const ON_Font* font
  )
{
  for(;;)
  {
    if (nullptr == font)
      break;
    font = font->ManagedFont();
    if (nullptr == font)
      break;
  
    const ON__UINT_PTR ft_face_as_uint = ON_Font::FreeTypeFace(font);
    if (0 == ft_face_as_uint)
      break;
    
    FT_Face ft_face = (FT_Face)ft_face_as_uint;
    
    unsigned int freetypeUPM =  (unsigned int)ft_face->units_per_EM;
    if (freetypeUPM > 0 && freetypeUPM < 0xFFFFFFF)
      return freetypeUPM;
    
    break;
  }
  
  return 0;
}

#if defined(ON_RUNTIME_APPLE)
#include "opennurbs_apple_nsfont.h"
#endif

void ON_FreeTypeGetFontMetrics(
  const ON_Font* font,
  ON_FontMetrics& font_unit_font_metrics
)
{
  font_unit_font_metrics = ON_FontMetrics::Unset;

  font = font->ManagedFont();
  if (nullptr == font)
    return;

  const ON__UINT_PTR ft_face_as_uint = ON_Font::FreeTypeFace(font);
  if (0 == ft_face_as_uint)
    return;

  FT_Face ft_face = (FT_Face)ft_face_as_uint;

  /*

  if (nullptr == glyph || false == glyph->CodePointIsSet())
    return 0;

  const ON_Font* font = glyph->Font();
  if (nullptr == font)
    return 0;
  font = font->ManagedFont();
  if (nullptr == font)
    return 0;


    
  const unsigned int glyph_id 
    = glyph->FontGlyphIdIsSet()
    ? (unsigned int)glyph->FontGlyphId()
    : ON_FreeType::GlyphId(face, glyph->CodePoint());
  if (0 == glyph_id)
    return 0;
    */
  
  // Turns out that checking H and I doesn't work very well for some
  // fonts designed for Asian languages, symbol fonts, and emoji fonts.
  const ON_FontGlyph Iglyph(font, 'I');
  ON_TextBox Ibox = ON_TextBox::Unset;
  const int ascent_of_I
    = (0 != ON_FreeTypeGetGlyphMetrics(&Iglyph, Ibox) && Ibox.IsSet() && Ibox.m_bbmax.j > 0)
    ? Ibox.m_bbmax.j
    : 0;

  const ON_FontGlyph Hglyph(font, 'H');
  ON_TextBox Hbox = ON_TextBox::Unset;
  const int ascent_of_H
    = (0 != ON_FreeTypeGetGlyphMetrics(&Hglyph, Hbox) && Hbox.IsSet() && Hbox.m_bbmax.j > 0)
    ? Hbox.m_bbmax.j
    : 0;

  const int ascent_of_capital = (ascent_of_H >= ascent_of_I) ? ascent_of_H : ascent_of_I;

  font_unit_font_metrics.SetAscentOfCapital(ascent_of_capital);

  font_unit_font_metrics.SetHeights(
    ft_face->ascender,
    ft_face->descender,
    ft_face->units_per_EM,
    ft_face->height
  );

  font_unit_font_metrics.SetUnderscore(
    ft_face->underline_position,
    ft_face->underline_thickness
  );

  // Turns out there are better strikeout values in DWRITE  information.
  double h = (double)((ascent_of_capital > 0) ? ascent_of_capital : ft_face->ascender);
  font_unit_font_metrics.SetStrikeout(
    (0.5*h),
    (0.5*((double)ft_face->underline_thickness))
  );

}

/////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////

class ON_FreeTypeOutlineAccumlator : public ON_OutlineAccumulator
{
public:
  ON_FreeTypeOutlineAccumlator() = default;
  ~ON_FreeTypeOutlineAccumlator() = default;

  bool AddFreeTypeFiguresToOutline(
    FT_Face ft_face, 
    FT_UInt font_glyph_id, 
    ON_Outline& outline
  );

private:
  ON_FreeTypeOutlineAccumlator(const ON_FreeTypeOutlineAccumlator&) = delete;
  ON_FreeTypeOutlineAccumlator& operator=(const ON_FreeTypeOutlineAccumlator&) = delete;

//public:
//  bool BeginGlyphOutline(
//    ON__UINT32 font_units_per_em,
//    bool bSingleStrokeFont,
//    ON_Outline* destination_outline,
//    FT_Face ft_face, FT_UInt font_glyph_id, FT_Orientation* ft_orientation
//  );
//  
//  bool BeginGlyphOutline(
//    ON__UINT32 font_units_per_em,
//    bool bSingleStrokeFont,
//    ON_Outline* destination_outline,
//    FT_Face ft_face, FT_UInt font_glyph_id, FT_Orientation* ft_orientation
//  );

private:
  FT_Orientation m_ft_orientation = FT_ORIENTATION_NONE;
  ON_OutlineFigurePoint::Type m_end_figure_point_type = ON_OutlineFigurePoint::Type::Unset;

  FT_Vector m_start_point;
  FT_Vector m_prev_point;

  static const ON_2fPoint Internal_To2fPoint(const FT_Vector* v) { return ON_2fPoint((float)(v->x), (float)(v->y)); }

  bool Internal_EndPreviousFigure()
  {
    // Freetype has a bad habit of leaving the last figure unclosed.
    const bool rc = (CurrentFigurePointCount() >= 2 && CurrentFigureAccumulating() && CurrentFigurePoint().IsOnFigure());
    if (rc)
      EndFigure(m_end_figure_point_type);
    return rc;
  }

  static int Internal_FreeTypeOutlineMoveToFunc(
    const FT_Vector* to,
    void* user
  );
  static int Internal_FreeTypeOutlineLineToFunc(
    const FT_Vector* to,
    void* user
  );
  static int Internal_FreeTypeOutlineConicToFunc(
    const FT_Vector* control,
    const FT_Vector* to,
    void* user
  );
  static int Internal_FreeTypeOutlineCubicToFunc(
    const FT_Vector* control1,
    const FT_Vector* control2,
    const FT_Vector* to,
    void* user
  );
  static int Internal_FreeTypeOutlineLineToCloseContourFunc(
    const FT_Vector* to,
    void* user
  );
};


const ON_TextBox ON_TextBox_CreateFromFreeTypeGlyphMetrics(
  const FT_Glyph_Metrics* ft_glyph_metrics
)
{
  if (nullptr == ft_glyph_metrics)
    return ON_TextBox::Unset;

  // Must use ft_load_flags that include FT_LOAD_NO_SCALE, in order
  // to get units are expressed in font design units.
  ON_TextBox glyph_metrics;
  glyph_metrics.m_bbmin.i = (int)(ft_glyph_metrics->horiBearingX);
  glyph_metrics.m_bbmin.j = (int)(ft_glyph_metrics->horiBearingY - ft_glyph_metrics->height);
  glyph_metrics.m_bbmax.i = (int)(ft_glyph_metrics->horiBearingX + ft_glyph_metrics->width);
  glyph_metrics.m_bbmax.j = (int)(ft_glyph_metrics->horiBearingY);
  glyph_metrics.m_advance.i = (int)(ft_glyph_metrics->horiAdvance);
  glyph_metrics.m_advance.j = (int)(ft_glyph_metrics->vertAdvance); // positive values mean downwards advance

  return glyph_metrics;
}

bool ON_FreeTypeOutlineAccumlator::AddFreeTypeFiguresToOutline(
  FT_Face ft_face, 
  FT_UInt font_glyph_id, 
  ON_Outline& outline
)
{
  bool rc = false;

  for (;;)
  {

    if (nullptr == ft_face)
      break;

    if (0 == font_glyph_id)
      break;

    if (false == ON_FreeTypeLoadGlyph((ON__UINT_PTR)ft_face, font_glyph_id, false))
      break;

    rc = true;

    const ON_TextBox glyph_metrics_in_font_design_units = ON_TextBox_CreateFromFreeTypeGlyphMetrics(&ft_face->glyph->metrics);
    outline.SetGlyphMetrics(glyph_metrics_in_font_design_units);

    FT_Glyph ft_glyph = nullptr;
    if (FT_Err_Ok != FT_Get_Glyph(ft_face->glyph, &ft_glyph))
      break;
    if (nullptr == ft_glyph)
      break;    

    if (FT_GLYPH_FORMAT_OUTLINE != ft_glyph->format)
      break;

    const FT_OutlineGlyph ft_outline_glyph = (FT_OutlineGlyph)ft_glyph;

    FT_Outline ft_outline = ft_outline_glyph->outline;

    if (ft_outline.n_points <= 0 || nullptr == ft_outline.points)
    {
      FT_Done_Glyph(ft_glyph);
      break;
    }

    m_ft_orientation = FT_Outline_Get_Orientation(&ft_outline);
    m_end_figure_point_type 
      = (ON_OutlineFigure::Type::SingleStroke == outline.FigureType())
     ? ON_OutlineFigurePoint::Type::EndFigureOpen
     : ON_OutlineFigurePoint::Type::EndFigureClosed;


    FT_Outline_Funcs ft_outline_funcs;
    memset(&ft_outline_funcs, 0, sizeof(ft_outline_funcs));
    ft_outline_funcs.move_to = ON_FreeTypeOutlineAccumlator::Internal_FreeTypeOutlineMoveToFunc;
    ft_outline_funcs.line_to = ON_FreeTypeOutlineAccumlator::Internal_FreeTypeOutlineLineToFunc;
    ft_outline_funcs.conic_to = ON_FreeTypeOutlineAccumlator::Internal_FreeTypeOutlineConicToFunc;
    ft_outline_funcs.cubic_to = ON_FreeTypeOutlineAccumlator::Internal_FreeTypeOutlineCubicToFunc;
    ft_outline_funcs.line_to_close_contour = ON_FreeTypeOutlineAccumlator::Internal_FreeTypeOutlineLineToCloseContourFunc;

    FT_Outline_Decompose(&ft_outline, &ft_outline_funcs, (void*)this);

    // Frees point memory
    FT_Done_Glyph(ft_glyph);

    // Freetype has a bad habit of leaving the last figure unclosed.
    Internal_EndPreviousFigure();

    break;
  }

  return rc;
}

int ON_FreeTypeOutlineAccumlator::Internal_FreeTypeOutlineMoveToFunc( 
  const FT_Vector* to, 
  void* user 
)
{
  ON_FreeTypeOutlineAccumlator* a = (ON_FreeTypeOutlineAccumlator*)user;
  if (nullptr == a)
    return FT_Err_Invalid_Argument;

  // freetype has a bad habit of failing to end the previous figure.
  a->Internal_EndPreviousFigure();

  a->BeginFigure(ON_OutlineFigurePoint::Type::BeginFigureUnknown, Internal_To2fPoint(to));
  a->m_start_point = *to;
  a->m_prev_point = *to;

  return FT_Err_Ok;
}

int ON_FreeTypeOutlineAccumlator::Internal_FreeTypeOutlineLineToFunc(
  const FT_Vector* to, 
  void* user 
)
{
  ON_FreeTypeOutlineAccumlator* a = (ON_FreeTypeOutlineAccumlator*)user;
  if (nullptr == a)
    return FT_Err_Invalid_Argument;

  a->AppendLine(Internal_To2fPoint(to));
  a->m_prev_point = *to;

  return FT_Err_Ok;
}

int ON_FreeTypeOutlineAccumlator::Internal_FreeTypeOutlineLineToCloseContourFunc(
  const FT_Vector* to, 
  void* user 
)
{
  ON_FreeTypeOutlineAccumlator* a = (ON_FreeTypeOutlineAccumlator*)user;
  if (nullptr == a)
    return FT_Err_Invalid_Argument;

  a->EndFigure(a->m_end_figure_point_type);
  a->m_prev_point = *to;
  
  return FT_Err_Ok;
}

int ON_FreeTypeOutlineAccumlator::Internal_FreeTypeOutlineConicToFunc(
  const FT_Vector* control,
  const FT_Vector* to, 
  void* user 
)
{
  ON_FreeTypeOutlineAccumlator* a = (ON_FreeTypeOutlineAccumlator*)user;
  if (nullptr == a)
    return FT_Err_Invalid_Argument;   

  a->AppendQuadraticBezier(Internal_To2fPoint(control),Internal_To2fPoint(to));
  a->m_prev_point = *to;

  return FT_Err_Ok;
}

int ON_FreeTypeOutlineAccumlator::Internal_FreeTypeOutlineCubicToFunc(
  const FT_Vector* control1,
  const FT_Vector* control2,
  const FT_Vector* to, 
  void* user 
)
{
  ON_FreeTypeOutlineAccumlator* a = (ON_FreeTypeOutlineAccumlator*)user;
  if (nullptr == a)
    return FT_Err_Invalid_Argument;

  a->AppendCubicBezier(Internal_To2fPoint(control1),Internal_To2fPoint(control2),Internal_To2fPoint(to));
  a->m_prev_point = *to;

  return FT_Err_Ok;
}


bool ON_FreeTypeLoadGlyph(
  ON__UINT_PTR ft_face_ptr,
  unsigned int ft_glyph_index,
  bool bLoadRenderBitmap
)
{
  FT_Face ft_face = (FT_Face)ft_face_ptr;
  if (nullptr == ft_face)
    return false;

  const FT_UInt font_glyph_id = (FT_UInt)ft_glyph_index;
  if (0 == font_glyph_id)
    return false;

#if defined(ON_RUNTIME_WIN)
  const int pass0 = 1;
#else
  const int pass0 = 1;
#endif
  for (int pass = bLoadRenderBitmap ? 1 : pass0; pass < 2; pass++)
  {
    FT_Int32 ft_face_load_no_scale_flag 
      = (0 == pass)
      ? 0
      : FT_LOAD_NO_SCALE;


    if (0 == pass)
    {
      const FT_UInt ft_font_height
        = (0 == pass)
        ? ((FT_UInt)ON_Font::Constants::AnnotationFontCellHeight)
        : ((FT_UInt)ft_face->units_per_EM);
      /*
        Avoid use of FT_LOAD_NO_SCALE
        https://www.freetype.org/freetype2/docs/reference/ft2-base_interface.html#FT_LOAD_NO_SCALE
          If the font is ‘tricky’ (see FT_FACE_FLAG_TRICKY for more), using FT_LOAD_NO_SCALE 
          usually yields meaningless outlines because the subglyphs must be scaled and 
          positioned with hinting instructions. This can be solved by loading the font 
          without FT_LOAD_NO_SCALE and setting the character size to ‘font->units_per_EM’.
      */
#if defined(ON_RUNTIME_WIN)
      // Unit Systems:
      //  "ppt" = printers points = 1/72 inch
      // "lfu" = Windows logical font units = LOGFONT.lfHeight units
      // "linch" = "logical" inch units used by ::GetDeviceCaps(hdc, LOGPIXELS*)
      // "pixels" = "pixel" units used by ::GetDeviceCaps(hdc, LOGPIXELS*)
      HDC font_hdc = ON_Font::CreateWindowsLogfontDeviceContext();
      const int horiz_pixels_per_inch = ::GetDeviceCaps(font_hdc, LOGPIXELSX); // units = horiz pixels/"logical inch"
      const int vert_pixels_per_inch = ::GetDeviceCaps(font_hdc, LOGPIXELSY); // units = vertical pixels/"logical inch"
      ON_Font::DeleteWindowsLogfontDeviceContext(font_hdc);
      const double logical_font_height = ((double)ON_Font::Constants::AnnotationFontCellHeight); // units = lfu
      const double ppts_per_inch = 72.0; // printer points / inch
      // If "logical font units"/("pixels"/"logical inch") = "real" inch = 2.54 cm
      // then vpptr and hppts are in printers points.
      const double vppts = ppts_per_inch * logical_font_height / ((double)vert_pixels_per_inch);
      const double hppts = ppts_per_inch * logical_font_height / ((double)horiz_pixels_per_inch);

      const FT_F26Dot6 ft_char_width = (int)ceil(hppts*64.0);
      const FT_F26Dot6 ft_char_height = (int)ceil(vppts*64.0);
#else
      const FT_F26Dot6 ft_char_width = 0;
      const FT_F26Dot6 ft_char_height = 0;
#endif
      if (0 == ft_char_width || 0 == ft_char_height)
        continue;
      if (FT_Err_Ok != FT_Set_Char_Size(
        ft_face,
        ft_char_width,
        ft_char_height,
        ft_font_height,
        ft_font_height
      ))
      {
        continue;
      }
    }

    const FT_Int32 ft_load_outline_flags
      = FT_LOAD_NO_BITMAP
      | FT_LOAD_NO_HINTING
      | FT_LOAD_NO_AUTOHINT
      | ft_face_load_no_scale_flag
      | FT_LOAD_LINEAR_DESIGN
      | FT_LOAD_IGNORE_TRANSFORM
      ;

    const FT_Int32 ft_load_flags
      = (bLoadRenderBitmap)
      ? FT_LOAD_RENDER
      : ft_load_outline_flags;

    if (FT_Err_Ok != FT_Load_Glyph(ft_face, font_glyph_id, ft_load_flags))
      continue;

    return true;
  }

  return false;
}

// Returns font glyph id or 0
unsigned int ON_FreeTypeGetGlyphMetrics(
  const ON_FontGlyph* glyph,
  class ON_TextBox& glyph_metrics_in_font_design_units
)  
{
  glyph_metrics_in_font_design_units = ON_TextBox::Unset;

  if (nullptr == glyph || false == glyph->CodePointIsSet())
    return 0;

  const ON_Font* font = glyph->Font();
  if (nullptr == font)
    return 0;
  font = font->ManagedFont();
  if (nullptr == font)
    return 0;

  const ON__UINT_PTR ft_face_as_uint = ON_Font::FreeTypeFace(font);
  if (0 == ft_face_as_uint)
    return 0;

  FT_Face face = (FT_Face)ft_face_as_uint;

  const unsigned int glyph_index 
    = glyph->FontGlyphIndexIsSet()
    ? glyph->FontGlyphIndex()
    : ON_FreeType::GlyphId(face, glyph->CodePoint());
  if (0 == glyph_index)
    return 0;

  ////#if defined(ON_RUNTIME_APPLE) && defined(ON_DEBUG)
  ////  const unsigned int apple_glyph_id = ON_AppleNSFontGlyphIndex(font->AppleFont(), glyph->CodePoint());
  ////  if (glyph_id != apple_glyph_id)
  ////  {
  ////    // In an Aug 20, 2018 test, anytime glyph_id != apple_glyph_id, the freetype value
  ////    // was incorrect. AppleGothic is a good test case. When freetype and ON_AppleNSFontGlyphIndex
  ////    // are different, freetype is referencing a glyph index that font book doesn't show
  ////    // (and is unable to get outlines) or one that is a solid box (and freetype delivers
  ////    // a square for the glyph instead of the correct glyph).
  ////    ON_ERROR("ON_AppleNSFontGlyphIndex() failed.");
  ////    ON_AppleNSFontGlyphIndex(font->AppleFont(), glyph->CodePoint());
  ////  }
  ////#endif

  
  const bool bLoadRenderBitmap = false;
  // bLoadRenderBitmap = false means we load using FT_LOAD_NO_SCALE
  // This won't work for "tricky" font faces that render glyphs using composites.
  if (false == ON_FreeTypeLoadGlyph(ft_face_as_uint, glyph_index, bLoadRenderBitmap))
    return 0;

  if ( nullptr == face->glyph)
    return 0;
  
  // Because ft_load_flags includes FT_LOAD_NO_SCALE, the
  // face->glyph->metrics units are expressed in font design units.
  glyph_metrics_in_font_design_units = ON_TextBox_CreateFromFreeTypeGlyphMetrics(&face->glyph->metrics);

  return glyph_index;
}


bool ON_FreeTypeGetGlyphOutline(
  const ON_FontGlyph* glyph,
  ON_OutlineFigure::Type figure_type,
  class ON_Outline& outline
)
{
  const unsigned int glyph_index = 0;
  return ON_FreeTypeGetGlyphOutline(glyph, glyph_index, figure_type, outline);
}

bool ON_FreeTypeGetGlyphOutline(
  const ON_FontGlyph* glyph,
  unsigned int glyph_index,
  ON_OutlineFigure::Type figure_type,
  class ON_Outline& outline
)
{
  outline = ON_Outline::Unset;

  if (nullptr == glyph)
    return false;

  if (false == glyph->CodePointIsSet())
    return false;

  glyph = glyph->ManagedGlyph();

  const ON_Font* font = glyph->Font();
  if (nullptr == font)
    return false;

  if (ON_OutlineFigure::Type::Unset == figure_type)
  {
    ON_OutlineFigure::Type font_figure_type = font->OutlineFigureType();
    if (ON_OutlineFigure::Type::Unset != font_figure_type)
    {
      figure_type = font_figure_type;
    }
  }

  if (0 == glyph_index)
  {
    glyph_index = glyph->FontGlyphIndex();
    if (glyph_index <= 0)
      return false;
  }
  
  FT_Face ft_face = (FT_Face)(ON_Font::FreeTypeFace(font));
  if (nullptr == ft_face)
    return false;

  const ON_FontMetrics fm = font->FontUnitFontMetrics();

  ON_FreeTypeOutlineAccumlator a;

  bool rc = a.BeginGlyphOutline(fm.UPM(), figure_type, &outline);

  if (rc)
  {

    rc = a.AddFreeTypeFiguresToOutline(
      ft_face,
      (FT_UInt)glyph_index,
      outline
    );

    if (false == a.EndOutline())
      rc = false;
  }

  // Generally, AddFreeTypeFiguresToOutline() set the outline glyph metrics.
  if (false == outline.GlyphMetrics().IsSet())
    outline.SetGlyphMetrics(glyph->FontUnitGlyphBox());

  return rc;
}

void ON_Font::DumpFreeTypeFace(
  ON__UINT_PTR free_type_face_ptr,
  ON_TextLog& text_log
)
{
  FT_Face face = (FT_Face)free_type_face_ptr;
  if (nullptr == face)
  {
    text_log.Print("FT_Face nullptr\n");
    return;
  }

  text_log.Print(
    "FT_Face: face[%d] (%d faces in font definition)\n",
    face->face_index,
    face->num_faces
  );

  text_log.PushIndent();
  ON_wString s;

  s = face->family_name;
  text_log.Print("Family name = %ls\n", static_cast<const wchar_t*>(s));
  
  s = face->style_name;
  text_log.Print("Style name = %ls\n", static_cast<const wchar_t*>(s));
  
  FT_Long style_mask = 0xFFFF;
  s = ON_FreeType::StyleFlagsToString(face->style_flags);
  if ( 0 != (style_mask&face->style_flags) || s.IsNotEmpty() )
     text_log.Print( "Style flags = 0x%04x %ls\n", (style_mask&face->style_flags), static_cast<const wchar_t*>(s) );

  s = ON_FreeType::FaceFlagsToString(face->face_flags);
  if ( 0 != face->face_flags || s.IsNotEmpty() )
     text_log.Print( "Face flags = 0x%x %ls\n", (face->face_flags), static_cast<const wchar_t*>(s) );

  text_log.Print("%d glyphs\n", face->num_glyphs);
  text_log.Print("%d charmaps\n", face->num_charmaps);

  text_log.PushIndent();
  if (nullptr != face->charmaps)
  {
    for (int i = 0; i < face->num_charmaps; i++)
    {
      FT_CharMap cmap = face->charmaps[i];
      const wchar_t* damaged
        = ON_FreeType::IsDamagedCharMap(cmap)
        ? L"DAMAGED "
        : L"";
      s = ON_wString::FormatToString( L"%lscmap[%d]", damaged, i );
      if (nullptr == cmap)
        s += L"nullptr";
      else
      {
        const ON_wString s1 = ON_FreeType::CharmapPlatformEncodingDescription(cmap);
        s += ON_wString::FormatToString(L" %ls", static_cast<const wchar_t*>(s1));
      }
      text_log.Print("%ls\n", static_cast<const wchar_t*>(s));
    }
  }
  text_log.PopIndent();
  
  text_log.PopIndent();

  return;
}

void ON_Font::DumpFreeType(
  ON_TextLog& text_log
) const
{
  const ON_Font* managed_font = this->ManagedFont();
  if (nullptr == managed_font)
    return;
  if (nullptr == managed_font->m_free_type_face)
    return;
  if (this != managed_font && ON_Font::CompareFontCharacteristicsForExperts(true, false, *this, *managed_font))
  {
    text_log.Print("FreeType managed font = <%u>\n", managed_font->RuntimeSerialNumber());
  }
  ON_Font::DumpFreeTypeFace((ON__UINT_PTR)(managed_font->m_free_type_face->m_face), text_log);
}

#endif

