/* // // 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 . // //////////////////////////////////////////////////////////////// */ #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 © The FreeType //// Project (www.freetype.org). All rights reserved. //// """ //// //// Please replace 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(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(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(platform), static_cast(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(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(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(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 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 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 dwriteFontFileLoader = nullptr; hr = dwriteFontFile->GetLoader(&dwriteFontFileLoader); if (FAILED(hr)) return false; if (nullptr == dwriteFontFileLoader || nullptr == dwriteFontFileLoader.Get() ) return false; Microsoft::WRL::ComPtr 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 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 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 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(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) bool bIsSubstituteFont = false; f = ON_FreeType::Internal_CreateFaceFromAppleFont(font.AppleCTFont(bIsSubstituteFont)); #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(s)); s = face->style_name; text_log.Print("Style name = %ls\n", static_cast(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(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(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(s1)); } text_log.Print("%ls\n", static_cast(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