/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ /* * 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 #include #include #include #include #ifdef MACOSX #include #include #endif #include #include #include #include #include #include FontAttributes DevFontFromCTFontDescriptor( CTFontDescriptorRef pFD, bool* bFontEnabled ) { // all CoreText fonts are device fonts that can rotate just fine FontAttributes rDFA; rDFA.SetQuality( 0 ); // reset the font attributes rDFA.SetFamilyType( FAMILY_DONTKNOW ); rDFA.SetPitch( PITCH_VARIABLE ); rDFA.SetWidthType( WIDTH_NORMAL ); rDFA.SetWeight( WEIGHT_NORMAL ); rDFA.SetItalic( ITALIC_NONE ); rDFA.SetMicrosoftSymbolEncoded( false ); // get font name CFStringRef pLang = nullptr; CFStringRef pFamilyName = static_cast( CTFontDescriptorCopyLocalizedAttribute( pFD, kCTFontFamilyNameAttribute, &pLang )); if ( !pLang ) { if( pFamilyName ) { CFRelease( pFamilyName ); } pFamilyName = static_cast(CTFontDescriptorCopyAttribute( pFD, kCTFontFamilyNameAttribute )); } rDFA.SetFamilyName( GetOUString( pFamilyName ) ); // get font style CFStringRef pStyleName = static_cast(CTFontDescriptorCopyAttribute( pFD, kCTFontStyleNameAttribute )); rDFA.SetStyleName( GetOUString( pStyleName ) ); // get font-enabled status if( bFontEnabled ) { int bEnabled = TRUE; // by default (and when we're on macOS < 10.6) it's "enabled" CFNumberRef pEnabled = static_cast(CTFontDescriptorCopyAttribute( pFD, kCTFontEnabledAttribute )); CFNumberGetValue( pEnabled, kCFNumberIntType, &bEnabled ); *bFontEnabled = bEnabled; } // get font attributes CFDictionaryRef pAttrDict = static_cast(CTFontDescriptorCopyAttribute( pFD, kCTFontTraitsAttribute )); if (bFontEnabled && *bFontEnabled) { // Ignore font formats not supported. int nFormat; CFNumberRef pFormat = static_cast(CTFontDescriptorCopyAttribute(pFD, kCTFontFormatAttribute)); CFNumberGetValue(pFormat, kCFNumberIntType, &nFormat); if (nFormat == kCTFontFormatUnrecognized || nFormat == kCTFontFormatPostScript || nFormat == kCTFontFormatBitmap) { SAL_INFO("vcl.fonts", "Ignoring font with unsupported format: " << rDFA.GetFamilyName()); *bFontEnabled = false; } CFRelease(pFormat); } // get symbolic trait // TODO: use other traits such as MonoSpace/Condensed/Expanded or Vertical too SInt64 nSymbolTrait = 0; CFNumberRef pSymbolNum = nullptr; if( CFDictionaryGetValueIfPresent( pAttrDict, kCTFontSymbolicTrait, reinterpret_cast(&pSymbolNum) ) ) { CFNumberGetValue( pSymbolNum, kCFNumberSInt64Type, &nSymbolTrait ); if (nSymbolTrait & kCTFontMonoSpaceTrait) rDFA.SetPitch(PITCH_FIXED); } // get the font weight double fWeight = 0; CFNumberRef pWeightNum = static_cast(CFDictionaryGetValue( pAttrDict, kCTFontWeightTrait )); // tdf#140401 check if attribute is a nullptr if( pWeightNum ) CFNumberGetValue( pWeightNum, kCFNumberDoubleType, &fWeight ); int nInt = WEIGHT_NORMAL; // Special case fixes // tdf#67744: Courier Std Medium is always bold. We get a kCTFontWeightTrait of 0.23 which // surely must be wrong. if (rDFA.GetFamilyName() == "Courier Std" && (rDFA.GetStyleName() == "Medium" || rDFA.GetStyleName() == "Medium Oblique") && fWeight > 0.2) { fWeight = 0; } // tdf#68889: Ditto for Gill Sans MT Pro. Here I can kinda understand it, maybe the // kCTFontWeightTrait is intended to give a subjective "optical" impression of how the font // looks, and Gill Sans MT Pro Medium is kinda heavy. But with the way LibreOffice uses fonts, // we still should think of it as being "medium" weight. if (rDFA.GetFamilyName() == "Gill Sans MT Pro" && (rDFA.GetStyleName() == "Medium" || rDFA.GetStyleName() == "Medium Italic") && fWeight > 0.2) { fWeight = 0; } if( fWeight > 0 ) { nInt = rint(int(WEIGHT_NORMAL) + fWeight * ((WEIGHT_BLACK - WEIGHT_NORMAL)/0.68)); if( nInt > WEIGHT_BLACK ) { nInt = WEIGHT_BLACK; } } else if( fWeight < 0 ) { nInt = rint(int(WEIGHT_NORMAL) + fWeight * ((WEIGHT_NORMAL - WEIGHT_THIN)/0.8)); if( nInt < WEIGHT_THIN ) { nInt = WEIGHT_THIN; } } rDFA.SetWeight( static_cast(nInt) ); // get the font slant double fSlant = 0; CFNumberRef pSlantNum = static_cast(CFDictionaryGetValue( pAttrDict, kCTFontSlantTrait )); // tdf#140401 check if attribute is a nullptr if( pSlantNum ) CFNumberGetValue( pSlantNum, kCFNumberDoubleType, &fSlant ); if( fSlant >= 0.035 ) { rDFA.SetItalic( ITALIC_NORMAL ); } // get width trait double fWidth = 0; CFNumberRef pWidthNum = static_cast(CFDictionaryGetValue( pAttrDict, kCTFontWidthTrait )); // tdf#140401 check if attribute is a nullptr if( pWidthNum ) CFNumberGetValue( pWidthNum, kCFNumberDoubleType, &fWidth ); nInt = WIDTH_NORMAL; if( fWidth > 0 ) { nInt = rint( int(WIDTH_NORMAL) + fWidth * ((WIDTH_ULTRA_EXPANDED - WIDTH_NORMAL)/0.4)); if( nInt > WIDTH_ULTRA_EXPANDED ) { nInt = WIDTH_ULTRA_EXPANDED; } } else if( fWidth < 0 ) { nInt = rint( int(WIDTH_NORMAL) + fWidth * ((WIDTH_NORMAL - WIDTH_ULTRA_CONDENSED)/0.5)); if( nInt < WIDTH_ULTRA_CONDENSED ) { nInt = WIDTH_ULTRA_CONDENSED; } } rDFA.SetWidthType( static_cast(nInt) ); // release the attribute dict that we had copied CFRelease( pAttrDict ); // TODO? also use the HEAD table if available to get more attributes // CFDataRef CTFontCopyTable( CTFontRef, kCTFontTableHead, /*kCTFontTableOptionNoOptions*/kCTFontTableOptionExcludeSynthetic ); return rDFA; } static void fontEnumCallBack( const void* pValue, void* pContext ) { CTFontDescriptorRef pFD = static_cast(pValue); // tdf#163000 don't add any fonts with an 'hvgl' font table // macOS Sequoia added a new PingFangUI.ttc font file which // contains all of the PingFang font families. However, any // fonts loaded from this font file result in the following // failures: // - Skia renders font with wrong glyphs // - Export to PDF contain a damaged embedded font // Despite the fact that the fonts in this new font file have // a TrueType font type, they are missing a 'glyf' font table // and, instead, have a new, undefined 'hvgl' font table. See // the following link for more details about the new 'hvgl' // font table: // https://gitlab.freedesktop.org/freetype/freetype/-/issues/1281 CFNumberRef pFontFormat = static_cast(CTFontDescriptorCopyAttribute(pFD, kCTFontFormatAttribute)); if (pFontFormat) { bool bSkipFont = false; int nFontFormat; // At least for the PingFangUI.ttc font file, the font format is // different on macOS and iOS if (CFNumberGetValue(pFontFormat, kCFNumberIntType, &nFontFormat) && (nFontFormat == kCTFontFormatOpenTypeTrueType || nFontFormat == kCTFontFormatTrueType)) { CTFontRef pFont = CTFontCreateWithFontDescriptor(pFD, 0.0, nullptr); if (pFont) { CFArrayRef pFontTableTags = CTFontCopyAvailableTables(pFont, kCTFontTableOptionNoOptions); if (pFontTableTags) { bool bGlyfTableFound = false; bool bHvglTableFound = false; CFIndex nFontTableTagCount = CFArrayGetCount(pFontTableTags); for (CFIndex i = 0; i < nFontTableTagCount; i++) { CTFontTableTag nTag = reinterpret_cast(CFArrayGetValueAtIndex(pFontTableTags, i)); if (nTag == kCTFontTableGlyf) { bGlyfTableFound = true; break; } else if (nTag == 'hvgl') { bHvglTableFound = true; } } bSkipFont = !bGlyfTableFound && bHvglTableFound; CFRelease(pFontTableTags); } CFRelease(pFont); } } CFRelease(pFontFormat); if (bSkipFont) return; } bool bFontEnabled; FontAttributes rDFA = DevFontFromCTFontDescriptor( pFD, &bFontEnabled ); if( bFontEnabled) { rtl::Reference pFontData = new CoreTextFontFace( rDFA, pFD ); SystemFontList* pFontList = static_cast(pContext); pFontList->AddFont( pFontData.get() ); } } SystemFontList::SystemFontList() : mpCTFontCollection( nullptr ) , mpCTFontArray( nullptr ) {} SystemFontList::~SystemFontList() { maFontContainer.clear(); if( mpCTFontArray ) { CFRelease( mpCTFontArray ); } if( mpCTFontCollection ) { CFRelease( mpCTFontCollection ); } } void SystemFontList::AddFont( CoreTextFontFace* pFontData ) { sal_IntPtr nFontId = pFontData->GetFontId(); maFontContainer[ nFontId ] = pFontData; } void SystemFontList::AnnounceFonts( vcl::font::PhysicalFontCollection& rFontCollection ) const { for(const auto& rEntry : maFontContainer ) { rFontCollection.Add( rEntry.second.get() ); } } CoreTextFontFace* SystemFontList::GetFontDataFromId( sal_IntPtr nFontId ) const { auto it = maFontContainer.find( nFontId ); if( it == maFontContainer.end() ) { return nullptr; } return (*it).second.get(); } bool SystemFontList::Init() { // enumerate available system fonts static const int nMaxDictEntries = 8; CFMutableDictionaryRef pCFDict = CFDictionaryCreateMutable( nullptr, nMaxDictEntries, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks ); CFDictionaryAddValue( pCFDict, kCTFontCollectionRemoveDuplicatesOption, kCFBooleanTrue ); mpCTFontCollection = CTFontCollectionCreateFromAvailableFonts( pCFDict ); CFRelease( pCFDict ); mpCTFontArray = CTFontCollectionCreateMatchingFontDescriptors( mpCTFontCollection ); const int nFontCount = CFArrayGetCount( mpCTFontArray ); const CFRange aFullRange = CFRangeMake( 0, nFontCount ); CFArrayApplyFunction( mpCTFontArray, aFullRange, fontEnumCallBack, this ); return true; } std::unique_ptr GetCoretextFontList() { std::unique_ptr pList(new SystemFontList()); if( !pList->Init() ) { return nullptr; } return pList; } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */