/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ #include "coretext/common.h" #include "coretext/salcoretextfontutils.hxx" #include "quartz/utils.h" #include "sft.hxx" #ifdef MACOSX #include "coretext/salgdi.h" #include "aqua/salinst.h" #else // IOS #include "headless/svpinst.hxx" #endif static bool GetDevFontAttributes( CTFontDescriptorRef font_descriptor, ImplDevFontAttributes& rDFA ) { int value = 0; // reset the attributes rDFA.SetFamilyType( FAMILY_DONTKNOW ); rDFA.SetPitch( PITCH_VARIABLE ); rDFA.SetWidthType( WIDTH_NORMAL ); rDFA.SetWeight( WEIGHT_NORMAL ); rDFA.SetItalic( ITALIC_NONE ); rDFA.SetSymbolFlag( false ); rDFA.mbOrientation = true; rDFA.mbDevice = true; rDFA.mnQuality = 0; CFNumberRef format = (CFNumberRef)CTFontDescriptorCopyAttribute(font_descriptor, kCTFontFormatAttribute); CFNumberGetValue(format, kCFNumberIntType, &value); CFRelease(format); if(value == kCTFontFormatBitmap) { /* we don't want bitmap fonts */ return false; } rDFA.mbSubsettable = true; rDFA.mbEmbeddable = false; CFStringRef family_name = (CFStringRef)CTFontDescriptorCopyAttribute(font_descriptor, kCTFontFamilyNameAttribute); rDFA.SetFamilyName( GetOUString(family_name) ); CFRelease(family_name); CFDictionaryRef traits = (CFDictionaryRef)CTFontDescriptorCopyAttribute(font_descriptor, kCTFontTraitsAttribute); CFNumberRef symbolics = (CFNumberRef)CFDictionaryGetValue(traits, kCTFontSymbolicTrait); CFNumberGetValue(symbolics, kCFNumberIntType, &value); if(value & kCTFontMonoSpaceTrait) { rDFA.SetPitch( PITCH_FIXED ); } if(value & kCTFontItalicTrait) { rDFA.SetItalic( ITALIC_NORMAL ); } if(value & kCTFontBoldTrait) { rDFA.SetWeight( WEIGHT_BOLD ); } if(value & kCTFontCondensedTrait) { rDFA.SetWidthType( WIDTH_CONDENSED ); } else if(value & kCTFontExpandedTrait) { rDFA.SetWidthType( WIDTH_EXPANDED ); } switch(value & kCTFontClassMaskTrait) { case kCTFontOldStyleSerifsClass: rDFA.SetFamilyType( FAMILY_ROMAN ); break; case kCTFontTransitionalSerifsClass: case kCTFontModernSerifsClass: case kCTFontClarendonSerifsClass: case kCTFontSlabSerifsClass: case kCTFontFreeformSerifsClass: break; case kCTFontSansSerifClass: rDFA.SetFamilyType( FAMILY_SWISS ); break; case kCTFontOrnamentalsClass: rDFA.SetFamilyType( FAMILY_DECORATIVE ); break; case kCTFontScriptsClass: rDFA.SetFamilyType( FAMILY_SCRIPT ); break; case kCTFontSymbolicClass: rDFA.SetSymbolFlag( true ); break; } CFNumberRef weight = (CFNumberRef)CFDictionaryGetValue(traits, kCTFontWeightTrait); float fdval = 0.0; CFNumberGetValue(weight, kCFNumberFloatType, &fdval); if(fdval > 0.6) { rDFA.SetWeight( WEIGHT_BLACK ); } else if(fdval > 0.4) { rDFA.SetWeight( WEIGHT_ULTRABOLD ); } else if (fdval > 0.3) { rDFA.SetWeight( WEIGHT_BOLD ); } else if (fdval > 0.0) { rDFA.SetWeight( WEIGHT_SEMIBOLD ); } else if (fdval <= -0.8) { rDFA.SetWeight( WEIGHT_ULTRALIGHT ); } else if (fdval <= -0.4) { rDFA.SetWeight( WEIGHT_LIGHT ); } else if (fdval <= -0.3) { rDFA.SetWeight( WEIGHT_SEMILIGHT ); } else if (fdval <= -0.2) { rDFA.SetWeight( WEIGHT_THIN ); } else { rDFA.SetWeight( WEIGHT_NORMAL ); } CFStringRef string_ref = (CFStringRef)CTFontDescriptorCopyAttribute(font_descriptor, kCTFontStyleNameAttribute); OUString style(GetOUString(string_ref).toAsciiLowerCase()); CFRelease(string_ref); // heuristics to adjust font slant if( (style.indexOf("oblique") != -1) || (style.indexOf("inclined") != -1) || (style.indexOf("slanted") != -1) ) { rDFA.SetItalic( ITALIC_OBLIQUE ); } // heuristics to adjust font width if (style.indexOf("narrow") != -1) { rDFA.SetWidthType( WIDTH_SEMI_CONDENSED ); } // heuristics for font family type if( (style.indexOf("script") != -1) || (style.indexOf("chancery") != -1) ) { rDFA.SetFamilyType( FAMILY_SCRIPT ); } else if( (style.indexOf("comic") != -1) || (style.indexOf("outline") != -1) || (style.indexOf("pinpoint") != -1) ) { rDFA.SetFamilyType( FAMILY_DECORATIVE ); } else if( (style.indexOf("sans") != -1) ) { rDFA.SetFamilyType( FAMILY_SWISS ); } else if( (style.indexOf("roman") != -1) ) { rDFA.SetFamilyType( FAMILY_ROMAN ); } return true; } SystemFontList::SystemFontList() { CTFontCollectionRef font_collection = CTFontCollectionCreateFromAvailableFonts(NULL); if(font_collection) { CFArrayRef font_descriptors = CTFontCollectionCreateMatchingFontDescriptors(font_collection); if(font_descriptors) { for(int i = 0; i < CFArrayGetCount(font_descriptors); i++) { CTFontDescriptorRef font_descriptor = (CTFontDescriptorRef)CFArrayGetValueAtIndex(font_descriptors, i); CTFontRef font = CTFontCreateWithFontDescriptor(font_descriptor, 0, NULL); if(font) { ImplDevFontAttributes devfont_attr; if(GetDevFontAttributes( font_descriptor, devfont_attr ) ) { CoreTextPhysicalFontFace* font_face = new CoreTextPhysicalFontFace(devfont_attr, font); if(font_face && font_face->GetCTFont()) { m_aFontContainer [ font_face->GetCTFont() ] = font_face; } } CFRelease(font); } } CFRelease(font_descriptors); } CFRelease(font_collection); } } SystemFontList::~SystemFontList() { CoreTextFontContainer::const_iterator it = m_aFontContainer.begin(); for(; it != m_aFontContainer.end(); ++it ) delete (*it).second; m_aFontContainer.clear(); } CoreTextPhysicalFontFace* SystemFontList::GetFontDataFromRef( CTFontRef font ) const { CoreTextFontContainer::const_iterator it = m_aFontContainer.find( font ); return it == m_aFontContainer.end() ? NULL : (*it).second; } void SystemFontList::AnnounceFonts( ImplDevFontList& rFontList ) const { CoreTextFontContainer::const_iterator it = m_aFontContainer.begin(); for(; it != m_aFontContainer.end(); ++it ) { rFontList.Add( (*it).second->Clone() ); } } CoreTextPhysicalFontFace::CoreTextPhysicalFontFace( const ImplDevFontAttributes& rDFA, CTFontRef font ) : PhysicalFontFace( rDFA, 0 ) , m_CTFontRef((CTFontRef)CFRetain(font)) , m_pCharMap( NULL ) , m_bHasOs2Table( false ) , m_bOs2TableRead( false ) , m_bCmapTableRead( false ) , m_bHasCJKSupport( false ) , m_bFontCapabilitiesRead( false ) { } CoreTextPhysicalFontFace::~CoreTextPhysicalFontFace() { if( m_pCharMap ) { m_pCharMap->DeReference(); } SafeCFRelease(m_CTFontRef); } PhysicalFontFace* CoreTextPhysicalFontFace::Clone() const { CoreTextPhysicalFontFace* pClone = new CoreTextPhysicalFontFace(*this); if( m_pCharMap ) { m_pCharMap->AddReference(); } if( m_CTFontRef ) { pClone->m_CTFontRef = (CTFontRef)CFRetain(m_CTFontRef); } return pClone; } ImplFontEntry* CoreTextPhysicalFontFace::CreateFontInstance(FontSelectPattern& rFSD) const { return new ImplFontEntry(rFSD); } const ImplFontCharMap* CoreTextPhysicalFontFace::GetImplFontCharMap() { // return the cached charmap if( m_pCharMap ) { return m_pCharMap; } // set the default charmap m_pCharMap = ImplFontCharMap::GetDefaultMap(); m_pCharMap->AddReference(); // get the CMAP byte size CFDataRef rCmapTable = CTFontCopyTable( m_CTFontRef, kCTFontTableCmap, kCTFontTableOptionNoOptions); if(!rCmapTable) { return m_pCharMap; } if(!m_bCmapTableRead) { m_bCmapTableRead = true; DetermineCJKSupport_cmap(rCmapTable); } // parse the CMAP CmapResult aCmapResult; if(ParseCMAP( CFDataGetBytePtr(rCmapTable), CFDataGetLength(rCmapTable), aCmapResult ) ) { m_pCharMap = new ImplFontCharMap( aCmapResult ); m_pCharMap->AddReference(); } CFRelease(rCmapTable); return m_pCharMap; } bool CoreTextPhysicalFontFace::GetImplFontCapabilities(vcl::FontCapabilities &rFontCapabilities) { // read this only once per font if( m_bFontCapabilitiesRead ) { rFontCapabilities = m_aFontCapabilities; return !rFontCapabilities.maUnicodeRange.empty() || !rFontCapabilities.maCodePageRange.empty(); } m_bFontCapabilitiesRead = true; // get the GSUB table raw data CFDataRef rGSUBTable = CTFontCopyTable( m_CTFontRef, kCTFontTableGSUB, kCTFontTableOptionNoOptions); if(rGSUBTable) { vcl::getTTScripts(m_aFontCapabilities.maGSUBScriptTags, CFDataGetBytePtr(rGSUBTable), CFDataGetLength(rGSUBTable)); CFRelease(rGSUBTable); } CFDataRef OS2_Table = CTFontCopyTable( m_CTFontRef, kCTFontTableOS2, kCTFontTableOptionNoOptions); if(OS2_Table) { vcl::getTTCoverage( m_aFontCapabilities.maUnicodeRange, m_aFontCapabilities.maCodePageRange, CFDataGetBytePtr(OS2_Table), CFDataGetLength(OS2_Table)); /* while we are at it let's solve HasCJK for the same price */ if(!m_bOs2TableRead ) { m_bOs2TableRead = true; m_bHasOs2Table = true; DetermineCJKSupport_OS2(OS2_Table); } CFRelease(OS2_Table); } rFontCapabilities = m_aFontCapabilities; return !rFontCapabilities.maUnicodeRange.empty() || !rFontCapabilities.maCodePageRange.empty(); } struct font_table { unsigned char* table; unsigned char* dir_entry; unsigned char* cursor; }; void addTable(struct font_table* table, CTFontTableTag tag, CFDataRef data) { if(data && CFDataGetLength(data) > 0) { *(uint32_t*)table->dir_entry = CFSwapInt32HostToBig(tag); table->dir_entry += 4; *(uint32_t*)table->dir_entry = 0; /* TODO: checksum */ table->dir_entry += 4; *(uint32_t*)table->dir_entry = CFSwapInt32HostToBig((uint32_t)((uintptr_t)table->cursor - (uintptr_t)table)); table->dir_entry += 4; *(uint32_t*)table->dir_entry = CFSwapInt32HostToBig(CFDataGetLength(data)); table->dir_entry += 4; memcpy(table->cursor, CFDataGetBytePtr(data), CFDataGetLength(data)); table->cursor += CFDataGetLength(data); } } bool CoreTextPhysicalFontFace::GetRawFontData( std::vector& rBuffer, bool* pJustCFF ) const { bool rc; int table_count = 0; CFDataRef CFF_table = CTFontCopyTable( m_CTFontRef, kCTFontTableCFF, kCTFontTableOptionNoOptions); if(pJustCFF) { if(CFF_table) { *pJustCFF = CFDataGetLength(CFF_table) ? true : false; CFRelease(CFF_table); return true; } else { return false; } } size_t total_len = 0; CFDataRef head_table = CTFontCopyTable( m_CTFontRef, kCTFontTableHead, kCTFontTableOptionNoOptions); CFDataRef maxp_table = CTFontCopyTable( m_CTFontRef, kCTFontTableMaxp, kCTFontTableOptionNoOptions); CFDataRef cmap_table = CTFontCopyTable( m_CTFontRef, kCTFontTableHead, kCTFontTableOptionNoOptions); CFDataRef name_table = CTFontCopyTable( m_CTFontRef, kCTFontTableName, kCTFontTableOptionNoOptions); CFDataRef hhea_table = CTFontCopyTable( m_CTFontRef, kCTFontTableHhea, kCTFontTableOptionNoOptions); CFDataRef hmtx_table = CTFontCopyTable( m_CTFontRef, kCTFontTableHmtx, kCTFontTableOptionNoOptions); rc = false; if(head_table && maxp_table && cmap_table && name_table && hhea_table && hmtx_table) { if(CFDataGetLength(head_table) && CFDataGetLength(maxp_table) && CFDataGetLength(name_table) && CFDataGetLength(hhea_table) && CFDataGetLength(hmtx_table)) { table_count += 6; total_len = CFDataGetLength(head_table) + CFDataGetLength(maxp_table) + CFDataGetLength(name_table) + CFDataGetLength(hhea_table) + CFDataGetLength(hmtx_table); rc = true; } } CFDataRef loca_table = NULL; CFDataRef glyf_table = NULL; CFDataRef prep_table = NULL; CFDataRef cvt_table = NULL; CFDataRef fpgm_table = NULL; if(rc) { if(!CFF_table || CFDataGetLength(CFF_table) == 0) { loca_table = CTFontCopyTable( m_CTFontRef, kCTFontTableLoca, kCTFontTableOptionNoOptions); glyf_table = CTFontCopyTable( m_CTFontRef, kCTFontTableGlyf, kCTFontTableOptionNoOptions); if(!loca_table || !glyf_table || !CFDataGetLength(loca_table) || !CFDataGetLength(glyf_table)) { rc = false; } else { table_count += 2; total_len += CFDataGetLength(loca_table) + CFDataGetLength(glyf_table); prep_table = CTFontCopyTable( m_CTFontRef, kCTFontTablePrep, kCTFontTableOptionNoOptions); cvt_table = CTFontCopyTable( m_CTFontRef, kCTFontTableCvt, kCTFontTableOptionNoOptions); fpgm_table = CTFontCopyTable( m_CTFontRef, kCTFontTableFpgm, kCTFontTableOptionNoOptions); if(prep_table || CFDataGetLength(prep_table) > 0) { table_count += 1; total_len += CFDataGetLength(prep_table); } if(cvt_table || CFDataGetLength(cvt_table) > 0) { table_count += 1; total_len += CFDataGetLength(cvt_table); } if(fpgm_table || CFDataGetLength(fpgm_table) > 0) { table_count += 1; total_len += CFDataGetLength(fpgm_table); } } } else { table_count += 1; total_len += CFDataGetLength(CFF_table); } } if(rc) { total_len += 12 + 16 * table_count; rBuffer.resize(total_len); struct font_table table; unsigned char* cursor = &rBuffer[0]; int nLog2 = 0; while( (table_count >> nLog2) > 1 ) ++nLog2; table.table = cursor; *(uint16_t*)cursor = CFSwapInt16HostToBig(1); cursor += 2; *(uint16_t*)cursor = 0; cursor += 2; *(uint16_t*)cursor = CFSwapInt16HostToBig(table_count); cursor += 2; *(uint16_t*)cursor = CFSwapInt16HostToBig(nLog2 * 16); cursor += 2; *(uint16_t*)cursor = CFSwapInt16HostToBig(nLog2); cursor += 2; *(uint16_t*)cursor = CFSwapInt16HostToBig((table_count - nLog2) * 16); // rangeShift cursor += 2; table.dir_entry = cursor; cursor += (16 * table_count); table.cursor = cursor; addTable(&table, kCTFontTableCmap, cmap_table); addTable(&table, kCTFontTableCvt, cvt_table); addTable(&table, kCTFontTableFpgm, fpgm_table); addTable(&table, kCTFontTableCFF, CFF_table); addTable(&table, kCTFontTableGlyf, glyf_table); addTable(&table, kCTFontTableLoca, loca_table); addTable(&table, kCTFontTableHead, head_table); addTable(&table, kCTFontTableHhea, hhea_table); addTable(&table, kCTFontTableHmtx, hmtx_table); addTable(&table, kCTFontTableMaxp, maxp_table); addTable(&table, kCTFontTableName, name_table); addTable(&table, kCTFontTablePrep, prep_table); } SafeCFRelease(cmap_table); SafeCFRelease(cvt_table); SafeCFRelease(fpgm_table); SafeCFRelease(CFF_table); SafeCFRelease(glyf_table); SafeCFRelease(loca_table); SafeCFRelease(head_table); SafeCFRelease(hhea_table); SafeCFRelease(hmtx_table); SafeCFRelease(maxp_table); SafeCFRelease(name_table); SafeCFRelease(prep_table); return rc; } void CoreTextPhysicalFontFace::DetermineCJKSupport_OS2(CFDataRef rOS2Table) { if(CFDataGetLength(rOS2Table) >= 48) { const unsigned short* pOS2buffer = (const unsigned short*)CFDataGetBytePtr(rOS2Table); const unsigned short version = CFSwapInt16BigToHost(pOS2buffer[0]); if( version >= 1) { const unsigned short unicode_range = CFSwapInt16BigToHost(pOS2buffer[23]); if( unicode_range & 0x2DF0) { m_bHasCJKSupport = true; } } } } void CoreTextPhysicalFontFace::DetermineCJKSupport_cmap(CFDataRef rCmapTable) { int table_len = CFDataGetLength(rCmapTable) / 2; if(table_len >= 12) { const unsigned short* pCmap = (const unsigned short*)CFDataGetBytePtr(rCmapTable); if(pCmap[0] == 0) { short nb_sub_tables = CFSwapInt16BigToHost(pCmap[1]); for(int i = 2; --nb_sub_tables >= 0 && i < table_len; i += 4) { short platform = CFSwapInt16BigToHost(pCmap[i]); if( platform == kFontMacintoshPlatform ) { short encoding = CFSwapInt16BigToHost(pCmap[i+1]); if( encoding == kFontJapaneseScript || encoding == kFontTraditionalChineseScript || encoding == kFontKoreanScript || encoding == kFontSimpleChineseScript ) { m_bHasCJKSupport = true; break; } } } } } } bool CoreTextPhysicalFontFace::HasCJKSupport( void ) { // read this only once per font if(!m_bOs2TableRead ) { m_bOs2TableRead = true; CFDataRef rOS2Table = CTFontCopyTable( m_CTFontRef, kCTFontTableOS2, kCTFontTableOptionNoOptions); if(rOS2Table) { m_bHasOs2Table = true; DetermineCJKSupport_OS2(rOS2Table); CFRelease(rOS2Table); } } if( !m_bCmapTableRead && !m_bHasOs2Table && !m_bHasCJKSupport ) { m_bCmapTableRead = true; CFDataRef rCmapTable = CTFontCopyTable( m_CTFontRef, kCTFontTableCmap, kCTFontTableOptionNoOptions); if(rCmapTable) { DetermineCJKSupport_cmap(rCmapTable); CFRelease(rCmapTable); } } return m_bHasCJKSupport; } std::ostream &operator <<(std::ostream& s, CTFontRef pFont) { #ifndef SAL_LOG_INFO (void) pFont; #else if (pFont) { CFStringRef fontString = CTFontCopyFullName(pFont); s << "{" << GetOUString(fontString) << "@" << CTFontGetSize(pFont) << "}"; CFRelease(fontString); } else { s << "NULL"; } #endif return s; } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */