diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
commit | ed5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch) | |
tree | 7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /vcl/unx/generic/fontmanager/fontmanager.cxx | |
parent | Initial commit. (diff) | |
download | libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.tar.xz libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.zip |
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vcl/unx/generic/fontmanager/fontmanager.cxx')
-rw-r--r-- | vcl/unx/generic/fontmanager/fontmanager.cxx | 1147 |
1 files changed, 1147 insertions, 0 deletions
diff --git a/vcl/unx/generic/fontmanager/fontmanager.cxx b/vcl/unx/generic/fontmanager/fontmanager.cxx new file mode 100644 index 000000000..46e8ae162 --- /dev/null +++ b/vcl/unx/generic/fontmanager/fontmanager.cxx @@ -0,0 +1,1147 @@ +/* -*- 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 <memory> +#include <unistd.h> +#include <osl/thread.h> + +#include <unx/fontmanager.hxx> +#include <fontsubset.hxx> +#include <impfontcharmap.hxx> +#include <unx/gendata.hxx> +#include <unx/helper.hxx> +#include <vcl/fontcharmap.hxx> + +#include <tools/urlobj.hxx> + +#include <osl/file.hxx> + +#include <rtl/ustrbuf.hxx> +#include <rtl/strbuf.hxx> + +#include <sal/macros.h> +#include <sal/log.hxx> + +#include <i18nlangtag/applelangid.hxx> + +#include <sft.hxx> + +#if OSL_DEBUG_LEVEL > 1 +#include <sys/times.h> +#include <stdio.h> +#endif + +#include <algorithm> +#include <set> + +#ifdef CALLGRIND_COMPILE +#include <valgrind/callgrind.h> +#endif + +#include <com/sun/star/beans/XMaterialHolder.hpp> + +using namespace vcl; +using namespace utl; +using namespace psp; +using namespace osl; +using namespace com::sun::star::uno; +using namespace com::sun::star::beans; +using namespace com::sun::star::lang; + +/* + * static helpers + */ + +static sal_uInt16 getUInt16BE( const sal_uInt8*& pBuffer ) +{ + sal_uInt16 nRet = static_cast<sal_uInt16>(pBuffer[1]) | + (static_cast<sal_uInt16>(pBuffer[0]) << 8); + pBuffer+=2; + return nRet; +} + +/* + * PrintFont implementations + */ +PrintFontManager::PrintFont::PrintFont() +: m_eFamilyStyle(FAMILY_DONTKNOW) +, m_eItalic(ITALIC_DONTKNOW) +, m_eWidth(WIDTH_DONTKNOW) +, m_eWeight(WEIGHT_DONTKNOW) +, m_ePitch(PITCH_DONTKNOW) +, m_aEncoding(RTL_TEXTENCODING_DONTKNOW) +, m_nAscend(0) +, m_nDescend(0) +, m_nLeading(0) +, m_nXMin(0) +, m_nYMin(0) +, m_nXMax(0) +, m_nYMax(0) +, m_nDirectory(0) +, m_nCollectionEntry(0) +, m_nVariationEntry(0) +{ +} + +/* + * one instance only + */ +PrintFontManager& PrintFontManager::get() +{ + GenericUnixSalData* const pSalData(GetGenericUnixSalData()); + assert(pSalData); + return *pSalData->GetPrintFontManager(); +} + +/* + * the PrintFontManager + */ + +PrintFontManager::PrintFontManager() + : m_nNextFontID( 1 ) + , m_nNextDirAtom( 1 ) + , m_aFontInstallerTimer("PrintFontManager m_aFontInstallerTimer") +{ + m_aFontInstallerTimer.SetInvokeHandler(LINK(this, PrintFontManager, autoInstallFontLangSupport)); + m_aFontInstallerTimer.SetTimeout(5000); +} + +PrintFontManager::~PrintFontManager() +{ + m_aFontInstallerTimer.Stop(); + deinitFontconfig(); +} + +OString PrintFontManager::getDirectory( int nAtom ) const +{ + std::unordered_map< int, OString >::const_iterator it( m_aAtomToDir.find( nAtom ) ); + return it != m_aAtomToDir.end() ? it->second : OString(); +} + +int PrintFontManager::getDirectoryAtom( const OString& rDirectory ) +{ + int nAtom = 0; + std::unordered_map< OString, int >::const_iterator it + ( m_aDirToAtom.find( rDirectory ) ); + if( it != m_aDirToAtom.end() ) + nAtom = it->second; + else + { + nAtom = m_nNextDirAtom++; + m_aDirToAtom[ rDirectory ] = nAtom; + m_aAtomToDir[ nAtom ] = rDirectory; + } + return nAtom; +} + +std::vector<fontID> PrintFontManager::addFontFile( std::u16string_view rFileUrl ) +{ + rtl_TextEncoding aEncoding = osl_getThreadTextEncoding(); + INetURLObject aPath( rFileUrl ); + OString aName(OUStringToOString(aPath.GetLastName(INetURLObject::DecodeMechanism::WithCharset, aEncoding), aEncoding)); + OString aDir( OUStringToOString( + INetURLObject::decode( aPath.GetPath(), INetURLObject::DecodeMechanism::WithCharset, aEncoding ), aEncoding ) ); + + int nDirID = getDirectoryAtom( aDir ); + std::vector<fontID> aFontIds = findFontFileIDs( nDirID, aName ); + if( aFontIds.empty() ) + { + std::vector<PrintFont> aNewFonts = analyzeFontFile(nDirID, aName); + for (auto & font : aNewFonts) + { + fontID nFontId = m_nNextFontID++; + m_aFonts[nFontId] = std::move(font); + m_aFontFileToFontID[ aName ].insert( nFontId ); + aFontIds.push_back(nFontId); + } + } + return aFontIds; +} + +std::vector<PrintFontManager::PrintFont> PrintFontManager::analyzeFontFile( int nDirID, const OString& rFontFile, const char *pFormat ) const +{ + std::vector<PrintFontManager::PrintFont> aNewFonts; + + OString aDir( getDirectory( nDirID ) ); + + OString aFullPath = aDir + "/" + rFontFile; + + // #i1872# reject unreadable files + if( access( aFullPath.getStr(), R_OK ) ) + return aNewFonts; + + bool bSupported = false; + if (pFormat) + { + if (!strcmp(pFormat, "TrueType") || + !strcmp(pFormat, "CFF")) + bSupported = true; + } + if (!bSupported) + { + OString aExt( rFontFile.copy( rFontFile.lastIndexOf( '.' )+1 ) ); + if( aExt.equalsIgnoreAsciiCase("ttf") + || aExt.equalsIgnoreAsciiCase("ttc") + || aExt.equalsIgnoreAsciiCase("tte") // #i33947# for Gaiji support + || aExt.equalsIgnoreAsciiCase("otf") ) // check for TTF- and PS-OpenType too + bSupported = true; + } + + if (bSupported) + { + // get number of ttc entries + int nLength = CountTTCFonts( aFullPath.getStr() ); + if (nLength > 0) + { + SAL_INFO("vcl.fonts", "ttc: " << aFullPath << " contains " << nLength << " fonts"); + + sal_uInt64 fileSize = 0; + + OUString aURL; + if (osl::File::getFileURLFromSystemPath(OStringToOUString(aFullPath, osl_getThreadTextEncoding()), + aURL) == osl::File::E_None) + { + osl::File aFile(aURL); + if (aFile.open(osl_File_OpenFlag_Read | osl_File_OpenFlag_NoLock) == osl::File::E_None) + { + osl::DirectoryItem aItem; + if (osl::DirectoryItem::get(aURL, aItem) == osl::File::E_None) + { + osl::FileStatus aFileStatus( osl_FileStatus_Mask_FileSize ); + if (aItem.getFileStatus(aFileStatus) == osl::File::E_None) + fileSize = aFileStatus.getFileSize(); + } + } + } + + //Feel free to calc the exact max possible number of fonts a file + //could contain given its physical size. But this will clamp it to + //a sane starting point + //http://processingjs.nihongoresources.com/the_smallest_font/ + //https://github.com/grzegorzrolek/null-ttf + const int nMaxFontsPossible = fileSize / 528; + if (nLength > nMaxFontsPossible) + nLength = nMaxFontsPossible; + + for( int i = 0; i < nLength; i++ ) + { + PrintFont aFont; + aFont.m_nDirectory = nDirID; + aFont.m_aFontFile = rFontFile; + aFont.m_nCollectionEntry = i; + if (analyzeSfntFile(aFont)) + aNewFonts.push_back(aFont); + } + } + else + { + PrintFont aFont; + aFont.m_nDirectory = nDirID; + aFont.m_aFontFile = rFontFile; + aFont.m_nCollectionEntry = 0; + + // need to read the font anyway to get aliases inside the font file + if (analyzeSfntFile(aFont)) + aNewFonts.push_back(aFont); + } + } + return aNewFonts; +} + +fontID PrintFontManager::findFontFileID(int nDirID, const OString& rFontFile, int nFaceIndex, int nVariationIndex) const +{ + fontID nID = 0; + + std::unordered_map< OString, ::std::set< fontID > >::const_iterator set_it = m_aFontFileToFontID.find( rFontFile ); + if( set_it == m_aFontFileToFontID.end() ) + return nID; + + for (auto const& elem : set_it->second) + { + auto it = m_aFonts.find(elem); + if( it == m_aFonts.end() ) + continue; + const PrintFont& rFont = (*it).second; + if (rFont.m_nDirectory == nDirID && + rFont.m_aFontFile == rFontFile && + rFont.m_nCollectionEntry == nFaceIndex && + rFont.m_nVariationEntry == nVariationIndex) + { + nID = it->first; + if (nID) + break; + } + } + + return nID; +} + +std::vector<fontID> PrintFontManager::findFontFileIDs( int nDirID, const OString& rFontFile ) const +{ + std::vector<fontID> aIds; + + std::unordered_map< OString, ::std::set< fontID > >::const_iterator set_it = m_aFontFileToFontID.find( rFontFile ); + if( set_it == m_aFontFileToFontID.end() ) + return aIds; + + for (auto const& elem : set_it->second) + { + auto it = m_aFonts.find(elem); + if( it == m_aFonts.end() ) + continue; + const PrintFont& rFont = (*it).second; + if (rFont.m_nDirectory == nDirID && + rFont.m_aFontFile == rFontFile) + aIds.push_back(it->first); + } + + return aIds; +} + +OUString PrintFontManager::convertSfntName( void* pRecord ) +{ + NameRecord* pNameRecord = static_cast<NameRecord*>(pRecord); + OUString aValue; + if( + ( pNameRecord->platformID == 3 && ( pNameRecord->encodingID == 0 || pNameRecord->encodingID == 1 ) ) // MS, Unicode + || + ( pNameRecord->platformID == 0 ) // Apple, Unicode + ) + { + OUStringBuffer aName( pNameRecord->slen/2 ); + const sal_uInt8* pNameBuffer = pNameRecord->sptr; + for(int n = 0; n < pNameRecord->slen/2; n++ ) + aName.append( static_cast<sal_Unicode>(getUInt16BE( pNameBuffer )) ); + aValue = aName.makeStringAndClear(); + } + else if( pNameRecord->platformID == 3 ) + { + if( pNameRecord->encodingID >= 2 && pNameRecord->encodingID <= 6 ) + { + /* + * and now for a special kind of madness: + * some fonts encode their byte value string as BE uint16 + * (leading to stray zero bytes in the string) + * while others code two bytes as a uint16 and swap to BE + */ + OStringBuffer aName; + const sal_uInt8* pNameBuffer = pNameRecord->sptr; + for(int n = 0; n < pNameRecord->slen/2; n++ ) + { + sal_Unicode aCode = static_cast<sal_Unicode>(getUInt16BE( pNameBuffer )); + char aChar = aCode >> 8; + if( aChar ) + aName.append( aChar ); + aChar = aCode & 0x00ff; + if( aChar ) + aName.append( aChar ); + } + switch( pNameRecord->encodingID ) + { + case 2: + aValue = OStringToOUString( aName, RTL_TEXTENCODING_MS_932 ); + break; + case 3: + aValue = OStringToOUString( aName, RTL_TEXTENCODING_MS_936 ); + break; + case 4: + aValue = OStringToOUString( aName, RTL_TEXTENCODING_MS_950 ); + break; + case 5: + aValue = OStringToOUString( aName, RTL_TEXTENCODING_MS_949 ); + break; + case 6: + aValue = OStringToOUString( aName, RTL_TEXTENCODING_MS_1361 ); + break; + } + } + } + else if( pNameRecord->platformID == 1 ) + { + OString aName(reinterpret_cast<char*>(pNameRecord->sptr), pNameRecord->slen); + rtl_TextEncoding eEncoding = RTL_TEXTENCODING_DONTKNOW; + switch (pNameRecord->encodingID) + { + case 0: + eEncoding = RTL_TEXTENCODING_APPLE_ROMAN; + break; + case 1: + eEncoding = RTL_TEXTENCODING_APPLE_JAPANESE; + break; + case 2: + eEncoding = RTL_TEXTENCODING_APPLE_CHINTRAD; + break; + case 3: + eEncoding = RTL_TEXTENCODING_APPLE_KOREAN; + break; + case 4: + eEncoding = RTL_TEXTENCODING_APPLE_ARABIC; + break; + case 5: + eEncoding = RTL_TEXTENCODING_APPLE_HEBREW; + break; + case 6: + eEncoding = RTL_TEXTENCODING_APPLE_GREEK; + break; + case 7: + eEncoding = RTL_TEXTENCODING_APPLE_CYRILLIC; + break; + case 9: + eEncoding = RTL_TEXTENCODING_APPLE_DEVANAGARI; + break; + case 10: + eEncoding = RTL_TEXTENCODING_APPLE_GURMUKHI; + break; + case 11: + eEncoding = RTL_TEXTENCODING_APPLE_GUJARATI; + break; + case 21: + eEncoding = RTL_TEXTENCODING_APPLE_THAI; + break; + case 25: + eEncoding = RTL_TEXTENCODING_APPLE_CHINSIMP; + break; + case 29: + eEncoding = RTL_TEXTENCODING_APPLE_CENTEURO; + break; + case 32: //Uninterpreted + eEncoding = RTL_TEXTENCODING_UTF8; + break; + default: + if (aName.startsWith("Khmer OS")) + eEncoding = RTL_TEXTENCODING_UTF8; + SAL_WARN_IF(eEncoding == RTL_TEXTENCODING_DONTKNOW, "vcl.fonts", "Unimplemented mac encoding " << pNameRecord->encodingID << " to unicode conversion for fontname " << aName); + break; + } + if (eEncoding != RTL_TEXTENCODING_DONTKNOW) + aValue = OStringToOUString(aName, eEncoding); + } + + return aValue; +} + +//fdo#33349.There exists an archaic Berling Antiqua font which has a "Times New +//Roman" name field in it. We don't want the "Times New Roman" name to take +//precedence in this case. We take Berling Antiqua as a higher priority name, +//and erase the "Times New Roman" name +namespace +{ + bool isBadTNR(std::u16string_view rName, ::std::set< OUString >& rSet) + { + bool bRet = false; + if ( rName == u"Berling Antiqua" ) + { + ::std::set< OUString >::iterator aEnd = rSet.end(); + ::std::set< OUString >::iterator aI = rSet.find("Times New Roman"); + if (aI != aEnd) + { + bRet = true; + rSet.erase(aI); + } + } + return bRet; + } +} + +void PrintFontManager::analyzeSfntFamilyName( void const * pTTFont, ::std::vector< OUString >& rNames ) +{ + OUString aFamily; + + rNames.clear(); + ::std::set< OUString > aSet; + + NameRecord* pNameRecords = nullptr; + int nNameRecords = GetTTNameRecords( static_cast<TrueTypeFont const *>(pTTFont), &pNameRecords ); + if( nNameRecords && pNameRecords ) + { + LanguageTag aSystem(""); + LanguageType eLang = aSystem.getLanguageType(); + int nLastMatch = -1; + for( int i = 0; i < nNameRecords; i++ ) + { + if( pNameRecords[i].nameID != 1 || pNameRecords[i].sptr == nullptr ) + continue; + int nMatch = -1; + if( pNameRecords[i].platformID == 0 ) // Unicode + nMatch = 4000; + else if( pNameRecords[i].platformID == 3 ) + { + // this bases on the LanguageType actually being a Win LCID + if (pNameRecords[i].languageID == eLang) + nMatch = 8000; + else if( pNameRecords[i].languageID == LANGUAGE_ENGLISH_US ) + nMatch = 2000; + else if( pNameRecords[i].languageID == LANGUAGE_ENGLISH || + pNameRecords[i].languageID == LANGUAGE_ENGLISH_UK ) + nMatch = 1500; + else + nMatch = 1000; + } + else if (pNameRecords[i].platformID == 1) + { + AppleLanguageId aAppleId = static_cast<AppleLanguageId>(static_cast<sal_uInt16>(pNameRecords[i].languageID)); + LanguageTag aApple(makeLanguageTagFromAppleLanguageId(aAppleId)); + if (aApple == aSystem) + nMatch = 8000; + else if (aAppleId == AppleLanguageId::ENGLISH) + nMatch = 2000; + else + nMatch = 1000; + } + OUString aName = convertSfntName( pNameRecords + i ); + aSet.insert( aName ); + if (aName.isEmpty()) + continue; + if( nMatch > nLastMatch || isBadTNR(aName, aSet) ) + { + nLastMatch = nMatch; + aFamily = aName; + } + } + } + DisposeNameRecords( pNameRecords, nNameRecords ); + if( !aFamily.isEmpty() ) + { + rNames.push_back( aFamily ); + for (auto const& elem : aSet) + if( elem != aFamily ) + rNames.push_back(elem); + } +} + +bool PrintFontManager::analyzeSfntFile( PrintFont& rFont ) const +{ + bool bSuccess = false; + rtl_TextEncoding aEncoding = osl_getThreadTextEncoding(); + OString aFile = getFontFile( rFont ); + TrueTypeFont* pTTFont = nullptr; + + auto const e = OpenTTFontFile( aFile.getStr(), rFont.m_nCollectionEntry, &pTTFont ); + if( e == SFErrCodes::Ok ) + { + TTGlobalFontInfo aInfo; + GetTTGlobalFontInfo( pTTFont, & aInfo ); + + ::std::vector< OUString > aNames; + analyzeSfntFamilyName( pTTFont, aNames ); + + // set family name from XLFD if possible + if (rFont.m_aFamilyName.isEmpty()) + { + if( !aNames.empty() ) + { + rFont.m_aFamilyName = aNames.front(); + aNames.erase(aNames.begin()); + } + else + { + sal_Int32 dotIndex; + + // poor font does not have a family name + // name it to file name minus the extension + dotIndex = rFont.m_aFontFile.lastIndexOf( '.' ); + if ( dotIndex == -1 ) + dotIndex = rFont.m_aFontFile.getLength(); + + rFont.m_aFamilyName = OStringToOUString(rFont.m_aFontFile.subView(0, dotIndex), aEncoding); + } + } + for (auto const& aAlias : aNames) + { + if (!aAlias.isEmpty()) + { + if (rFont.m_aFamilyName != aAlias) + { + auto al_it = std::find(rFont.m_aAliases.begin(), rFont.m_aAliases.end(), aAlias); + if( al_it == rFont.m_aAliases.end() ) + rFont.m_aAliases.push_back(aAlias); + } + } + } + + if( aInfo.usubfamily ) + rFont.m_aStyleName = OUString( aInfo.usubfamily ); + + SAL_WARN_IF( !aInfo.psname, "vcl.fonts", "No PostScript name in font:" << aFile ); + + rFont.m_aPSName = aInfo.psname ? + OUString(aInfo.psname, rtl_str_getLength(aInfo.psname), aEncoding) : + rFont.m_aFamilyName; // poor font does not have a postscript name + + rFont.m_eFamilyStyle = matchFamilyName(rFont.m_aFamilyName); + + switch( aInfo.weight ) + { + case FW_THIN: rFont.m_eWeight = WEIGHT_THIN; break; + case FW_EXTRALIGHT: rFont.m_eWeight = WEIGHT_ULTRALIGHT; break; + case FW_LIGHT: rFont.m_eWeight = WEIGHT_LIGHT; break; + case FW_MEDIUM: rFont.m_eWeight = WEIGHT_MEDIUM; break; + case FW_SEMIBOLD: rFont.m_eWeight = WEIGHT_SEMIBOLD; break; + case FW_BOLD: rFont.m_eWeight = WEIGHT_BOLD; break; + case FW_EXTRABOLD: rFont.m_eWeight = WEIGHT_ULTRABOLD; break; + case FW_BLACK: rFont.m_eWeight = WEIGHT_BLACK; break; + + case FW_NORMAL: + default: rFont.m_eWeight = WEIGHT_NORMAL; break; + } + + switch( aInfo.width ) + { + case FWIDTH_ULTRA_CONDENSED: rFont.m_eWidth = WIDTH_ULTRA_CONDENSED; break; + case FWIDTH_EXTRA_CONDENSED: rFont.m_eWidth = WIDTH_EXTRA_CONDENSED; break; + case FWIDTH_CONDENSED: rFont.m_eWidth = WIDTH_CONDENSED; break; + case FWIDTH_SEMI_CONDENSED: rFont.m_eWidth = WIDTH_SEMI_CONDENSED; break; + case FWIDTH_SEMI_EXPANDED: rFont.m_eWidth = WIDTH_SEMI_EXPANDED; break; + case FWIDTH_EXPANDED: rFont.m_eWidth = WIDTH_EXPANDED; break; + case FWIDTH_EXTRA_EXPANDED: rFont.m_eWidth = WIDTH_EXTRA_EXPANDED; break; + case FWIDTH_ULTRA_EXPANDED: rFont.m_eWidth = WIDTH_ULTRA_EXPANDED; break; + + case FWIDTH_NORMAL: + default: rFont.m_eWidth = WIDTH_NORMAL; break; + } + + rFont.m_ePitch = aInfo.pitch ? PITCH_FIXED : PITCH_VARIABLE; + rFont.m_eItalic = aInfo.italicAngle == 0 ? ITALIC_NONE : ( aInfo.italicAngle < 0 ? ITALIC_NORMAL : ITALIC_OBLIQUE ); + // #104264# there are fonts that set italic angle 0 although they are + // italic; use macstyle bit here + if( aInfo.italicAngle == 0 && (aInfo.macStyle & 2) ) + rFont.m_eItalic = ITALIC_NORMAL; + + rFont.m_aEncoding = aInfo.symbolEncoded ? RTL_TEXTENCODING_SYMBOL : RTL_TEXTENCODING_UCS2; + + if( aInfo.ascender && aInfo.descender ) + { + rFont.m_nLeading = aInfo.linegap; + rFont.m_nAscend = aInfo.ascender; + rFont.m_nDescend = -aInfo.descender; + } + else if( aInfo.typoAscender && aInfo.typoDescender ) + { + rFont.m_nLeading = aInfo.typoLineGap; + rFont.m_nAscend = aInfo.typoAscender; + rFont.m_nDescend = -aInfo.typoDescender; + } + else if( aInfo.winAscent && aInfo.winDescent ) + { + rFont.m_nAscend = aInfo.winAscent; + rFont.m_nDescend = aInfo.winDescent; + rFont.m_nLeading = rFont.m_nAscend + rFont.m_nDescend - 1000; + } + + // last try: font bounding box + if( rFont.m_nAscend == 0 ) + rFont.m_nAscend = aInfo.yMax; + if( rFont.m_nDescend == 0 ) + rFont.m_nDescend = -aInfo.yMin; + if( rFont.m_nLeading == 0 ) + rFont.m_nLeading = 15 * (rFont.m_nAscend+rFont.m_nDescend) / 100; + + // get bounding box + rFont.m_nXMin = aInfo.xMin; + rFont.m_nYMin = aInfo.yMin; + rFont.m_nXMax = aInfo.xMax; + rFont.m_nYMax = aInfo.yMax; + + CloseTTFont( pTTFont ); + bSuccess = true; + } + else + SAL_WARN("vcl.fonts", "Could not OpenTTFont \"" << aFile << "\": " << int(e)); + + return bSuccess; +} + +void PrintFontManager::initialize() +{ + #ifdef CALLGRIND_COMPILE + CALLGRIND_TOGGLE_COLLECT(); + CALLGRIND_ZERO_STATS(); + #endif + + // initialize can be called more than once, e.g. + // gtk-fontconfig-timestamp changes to reflect new font installed and + // PrintFontManager::initialize called again + { + m_nNextFontID = 1; + m_aFonts.clear(); + } +#if OSL_DEBUG_LEVEL > 1 + clock_t aStart; + clock_t aStep1; + clock_t aStep2; + + struct tms tms; + + aStart = times( &tms ); +#endif + + // first try fontconfig + initFontconfig(); + + // part one - look for downloadable fonts + rtl_TextEncoding aEncoding = osl_getThreadTextEncoding(); + const OUString &rSalPrivatePath = psp::getFontPath(); + + // search for the fonts in SAL_PRIVATE_FONTPATH first; those are + // the fonts installed with the office + if( !rSalPrivatePath.isEmpty() ) + { + OString aPath = OUStringToOString( rSalPrivatePath, aEncoding ); + sal_Int32 nIndex = 0; + do + { + OString aToken = aPath.getToken( 0, ';', nIndex ); + normPath( aToken ); + if (!aToken.isEmpty()) + addFontconfigDir(aToken); + } while( nIndex >= 0 ); + } + + countFontconfigFonts(); + +#if OSL_DEBUG_LEVEL > 1 + aStep1 = times( &tms ); + + aStep2 = times( &tms ); + SAL_INFO("vcl.fonts", "PrintFontManager::initialize: collected " + << m_aFonts.size() + << " fonts."); + double fTick = (double)sysconf( _SC_CLK_TCK ); + SAL_INFO("vcl.fonts", "Step 1 took " + << ((double)(aStep1 - aStart)/fTick) + << " seconds."); + SAL_INFO("vcl.fonts", "Step 2 took " + << ((double)(aStep2 - aStep1)/fTick) + << " seconds."); +#endif + + #ifdef CALLGRIND_COMPILE + CALLGRIND_DUMP_STATS(); + CALLGRIND_TOGGLE_COLLECT(); + #endif +} + +void PrintFontManager::getFontList( ::std::vector< fontID >& rFontIDs ) +{ + rFontIDs.clear(); + + for (auto const& font : m_aFonts) + rFontIDs.push_back(font.first); +} + +void PrintFontManager::fillPrintFontInfo(const PrintFont& rFont, FastPrintFontInfo& rInfo) +{ + rInfo.m_aFamilyName = rFont.m_aFamilyName; + rInfo.m_aStyleName = rFont.m_aStyleName; + rInfo.m_eFamilyStyle = rFont.m_eFamilyStyle; + rInfo.m_eItalic = rFont.m_eItalic; + rInfo.m_eWidth = rFont.m_eWidth; + rInfo.m_eWeight = rFont.m_eWeight; + rInfo.m_ePitch = rFont.m_ePitch; + rInfo.m_aEncoding = rFont.m_aEncoding; + rInfo.m_aAliases = rFont.m_aAliases; +} + +void PrintFontManager::fillPrintFontInfo( PrintFont& rFont, PrintFontInfo& rInfo ) const +{ + if (rFont.m_nAscend == 0 && rFont.m_nDescend == 0) + { + analyzeSfntFile(rFont); + } + + fillPrintFontInfo( rFont, static_cast< FastPrintFontInfo& >( rInfo ) ); + + rInfo.m_nAscend = rFont.m_nAscend; + rInfo.m_nDescend = rFont.m_nDescend; +} + +bool PrintFontManager::getFontInfo( fontID nFontID, PrintFontInfo& rInfo ) const +{ + const PrintFont* pFont = getFont( nFontID ); + if( pFont ) + { + rInfo.m_nID = nFontID; + fillPrintFontInfo( *pFont, rInfo ); + } + return pFont != nullptr; +} + +bool PrintFontManager::getFontFastInfo( fontID nFontID, FastPrintFontInfo& rInfo ) const +{ + const PrintFont* pFont = getFont( nFontID ); + if( pFont ) + { + rInfo.m_nID = nFontID; + fillPrintFontInfo( *pFont, rInfo ); + } + return pFont != nullptr; +} + +void PrintFontManager::getFontBoundingBox( fontID nFontID, int& xMin, int& yMin, int& xMax, int& yMax ) +{ + PrintFont* pFont = getFont( nFontID ); + if( pFont ) + { + if( pFont->m_nXMin == 0 && pFont->m_nYMin == 0 && pFont->m_nXMax == 0 && pFont->m_nYMax == 0 ) + { + analyzeSfntFile(*pFont); + } + xMin = pFont->m_nXMin; + yMin = pFont->m_nYMin; + xMax = pFont->m_nXMax; + yMax = pFont->m_nYMax; + } +} + +int PrintFontManager::getFontFaceNumber( fontID nFontID ) const +{ + int nRet = 0; + const PrintFont* pFont = getFont( nFontID ); + if (pFont) + { + nRet = pFont->m_nCollectionEntry; + if (nRet < 0) + nRet = 0; + } + return nRet; +} + +int PrintFontManager::getFontFaceVariation( fontID nFontID ) const +{ + int nRet = 0; + const PrintFont* pFont = getFont( nFontID ); + if (pFont) + { + nRet = pFont->m_nVariationEntry; + if (nRet < 0) + nRet = 0; + } + return nRet; +} + +FontFamily PrintFontManager::matchFamilyName( std::u16string_view rFamily ) +{ + struct family_t { + const char* mpName; + sal_uInt16 mnLength; + FontFamily meType; + }; + +#define InitializeClass( p, a ) p, sizeof(p) - 1, a + static const family_t pFamilyMatch[] = { + { InitializeClass( "arial", FAMILY_SWISS ) }, + { InitializeClass( "arioso", FAMILY_SCRIPT ) }, + { InitializeClass( "avant garde", FAMILY_SWISS ) }, + { InitializeClass( "avantgarde", FAMILY_SWISS ) }, + { InitializeClass( "bembo", FAMILY_ROMAN ) }, + { InitializeClass( "bookman", FAMILY_ROMAN ) }, + { InitializeClass( "conga", FAMILY_ROMAN ) }, + { InitializeClass( "courier", FAMILY_MODERN ) }, + { InitializeClass( "curl", FAMILY_SCRIPT ) }, + { InitializeClass( "fixed", FAMILY_MODERN ) }, + { InitializeClass( "gill", FAMILY_SWISS ) }, + { InitializeClass( "helmet", FAMILY_MODERN ) }, + { InitializeClass( "helvetica", FAMILY_SWISS ) }, + { InitializeClass( "international", FAMILY_MODERN ) }, + { InitializeClass( "lucida", FAMILY_SWISS ) }, + { InitializeClass( "new century schoolbook", FAMILY_ROMAN ) }, + { InitializeClass( "palatino", FAMILY_ROMAN ) }, + { InitializeClass( "roman", FAMILY_ROMAN ) }, + { InitializeClass( "sans serif", FAMILY_SWISS ) }, + { InitializeClass( "sansserif", FAMILY_SWISS ) }, + { InitializeClass( "serf", FAMILY_ROMAN ) }, + { InitializeClass( "serif", FAMILY_ROMAN ) }, + { InitializeClass( "times", FAMILY_ROMAN ) }, + { InitializeClass( "utopia", FAMILY_ROMAN ) }, + { InitializeClass( "zapf chancery", FAMILY_SCRIPT ) }, + { InitializeClass( "zapfchancery", FAMILY_SCRIPT ) } + }; + + OString aFamily = OUStringToOString( rFamily, RTL_TEXTENCODING_ASCII_US ); + sal_uInt32 nLower = 0; + sal_uInt32 nUpper = SAL_N_ELEMENTS(pFamilyMatch); + + while( nLower < nUpper ) + { + sal_uInt32 nCurrent = (nLower + nUpper) / 2; + const family_t* pHaystack = pFamilyMatch + nCurrent; + sal_Int32 nComparison = + rtl_str_compareIgnoreAsciiCase_WithLength + ( + aFamily.getStr(), aFamily.getLength(), + pHaystack->mpName, pHaystack->mnLength + ); + + if( nComparison < 0 ) + nUpper = nCurrent; + else + if( nComparison > 0 ) + nLower = nCurrent + 1; + else + return pHaystack->meType; + } + + return FAMILY_DONTKNOW; +} + +OString PrintFontManager::getFontFile(const PrintFont& rFont) const +{ + std::unordered_map< int, OString >::const_iterator it = m_aAtomToDir.find(rFont.m_nDirectory); + OString aPath = it->second + "/" + rFont.m_aFontFile; + return aPath; +} + +OUString PrintFontManager::getPSName( fontID nFontID ) +{ + PrintFont* pFont = getFont( nFontID ); + if (pFont && pFont->m_aPSName.isEmpty()) + { + analyzeSfntFile(*pFont); + } + + return pFont ? pFont->m_aPSName : OUString(); +} + +int PrintFontManager::getFontAscend( fontID nFontID ) +{ + PrintFont* pFont = getFont( nFontID ); + if (pFont && pFont->m_nAscend == 0 && pFont->m_nDescend == 0) + { + analyzeSfntFile(*pFont); + } + return pFont ? pFont->m_nAscend : 0; +} + +int PrintFontManager::getFontDescend( fontID nFontID ) +{ + PrintFont* pFont = getFont( nFontID ); + if (pFont && pFont->m_nAscend == 0 && pFont->m_nDescend == 0) + { + analyzeSfntFile(*pFont); + } + return pFont ? pFont->m_nDescend : 0; +} + +// TODO: move most of this stuff into the central font-subsetting code +bool PrintFontManager::createFontSubset( + FontSubsetInfo& rInfo, + fontID nFont, + const OUString& rOutFile, + const sal_GlyphId* pGlyphIds, + const sal_uInt8* pNewEncoding, + sal_Int32* pWidths, + int nGlyphs + ) +{ + PrintFont* pFont = getFont( nFont ); + if( !pFont ) + return false; + + rInfo.m_nFontType = FontType::SFNT_TTF; + + // reshuffle array of requested glyphs to make sure glyph0==notdef + sal_uInt8 pEnc[256]; + sal_uInt16 pGID[256]; + sal_uInt8 pOldIndex[256]; + memset( pEnc, 0, sizeof( pEnc ) ); + memset( pGID, 0, sizeof( pGID ) ); + memset( pOldIndex, 0, sizeof( pOldIndex ) ); + if( nGlyphs > 256 ) + return false; + int nChar = 1; + for( int i = 0; i < nGlyphs; i++ ) + { + if( pNewEncoding[i] == 0 ) + { + pOldIndex[ 0 ] = i; + } + else + { + SAL_WARN_IF( (pGlyphIds[i] & 0x007f0000), "vcl.fonts", "overlong glyph id" ); + SAL_WARN_IF( static_cast<int>(pNewEncoding[i]) >= nGlyphs, "vcl.fonts", "encoding wrong" ); + SAL_WARN_IF( pEnc[pNewEncoding[i]] != 0 || pGID[pNewEncoding[i]] != 0, "vcl.fonts", "duplicate encoded glyph" ); + pEnc[ pNewEncoding[i] ] = pNewEncoding[i]; + pGID[ pNewEncoding[i] ] = static_cast<sal_uInt16>(pGlyphIds[ i ]); + pOldIndex[ pNewEncoding[i] ] = i; + nChar++; + } + } + nGlyphs = nChar; // either input value or increased by one + + // prepare system name for read access for subset source file + // TODO: since this file is usually already mmapped there is no need to open it again + const OString aFromFile = getFontFile( *pFont ); + + TrueTypeFont* pTTFont = nullptr; // TODO: rename to SfntFont + if( OpenTTFontFile( aFromFile.getStr(), pFont->m_nCollectionEntry, &pTTFont ) != SFErrCodes::Ok ) + return false; + + // prepare system name for write access for subset file target + OUString aSysPath; + if( osl_File_E_None != osl_getSystemPathFromFileURL( rOutFile.pData, &aSysPath.pData ) ) + return false; + const rtl_TextEncoding aEncoding = osl_getThreadTextEncoding(); + const OString aToFile( OUStringToOString( aSysPath, aEncoding ) ); + + // do CFF subsetting if possible + sal_uInt32 nCffLength = 0; + const sal_uInt8* pCffBytes = pTTFont->table(vcl::O_CFF, nCffLength); + if (pCffBytes) + { + rInfo.LoadFont( FontType::CFF_FONT, pCffBytes, nCffLength ); +#if 1 // TODO: remove 16bit->long conversion when related methods handle non-16bit glyphids + sal_GlyphId aRequestedGlyphIds[256]; + for( int i = 0; i < nGlyphs; ++i ) + aRequestedGlyphIds[i] = pGID[i]; +#endif + // create subset file at requested path + FILE* pOutFile = fopen( aToFile.getStr(), "wb" ); + if (!pOutFile) + { + CloseTTFont( pTTFont ); + return false; + } + // create font subset + const char* const pGlyphSetName = nullptr; // TODO: better name? + const bool bOK = rInfo.CreateFontSubset( + FontType::TYPE1_PFB, + pOutFile, pGlyphSetName, + aRequestedGlyphIds, pEnc, nGlyphs, pWidths ); + fclose( pOutFile ); + // For OTC, values from hhea or OS2 are better + psp::PrintFontInfo aFontInfo; + if( getFontInfo( nFont, aFontInfo ) ) + { + rInfo.m_nAscent = aFontInfo.m_nAscend; + rInfo.m_nDescent = -aFontInfo.m_nDescend; + } + // cleanup before early return + CloseTTFont( pTTFont ); + return bOK; + } + + // do TTF->Type42 or Type3 subsetting + // fill in font info + psp::PrintFontInfo aFontInfo; + if( ! getFontInfo( nFont, aFontInfo ) ) + return false; + + rInfo.m_nAscent = aFontInfo.m_nAscend; + rInfo.m_nDescent = aFontInfo.m_nDescend; + rInfo.m_aPSName = getPSName( nFont ); + + int xMin, yMin, xMax, yMax; + getFontBoundingBox( nFont, xMin, yMin, xMax, yMax ); + rInfo.m_aFontBBox = tools::Rectangle( Point( xMin, yMin ), Size( xMax-xMin, yMax-yMin ) ); + rInfo.m_nCapHeight = yMax; // Well ... + + if (pWidths) + { + // fill in glyph advance widths + std::unique_ptr<sal_uInt16[]> pMetrics = GetTTSimpleGlyphMetrics( pTTFont, + pGID, + nGlyphs, + false/*bVertical*/ ); + if( pMetrics ) + { + for( int i = 0; i < nGlyphs; i++ ) + pWidths[pOldIndex[i]] = pMetrics[i]; + pMetrics.reset(); + } + else + { + CloseTTFont( pTTFont ); + return false; + } + } + + bool bSuccess = ( SFErrCodes::Ok == CreateTTFromTTGlyphs( pTTFont, + aToFile.getStr(), + pGID, + pEnc, + nGlyphs ) ); + CloseTTFont( pTTFont ); + + return bSuccess; +} + +void PrintFontManager::getGlyphWidths( fontID nFont, + bool bVertical, + std::vector< sal_Int32 >& rWidths, + std::map< sal_Unicode, sal_uInt32 >& rUnicodeEnc ) +{ + PrintFont* pFont = getFont( nFont ); + if (!pFont) + return; + TrueTypeFont* pTTFont = nullptr; + OString aFromFile = getFontFile( *pFont ); + if( OpenTTFontFile( aFromFile.getStr(), pFont->m_nCollectionEntry, &pTTFont ) != SFErrCodes::Ok ) + return; + int nGlyphs = pTTFont->glyphCount(); + if (nGlyphs > 0) + { + rWidths.resize(nGlyphs); + std::vector<sal_uInt16> aGlyphIds(nGlyphs); + for (int i = 0; i < nGlyphs; i++) + aGlyphIds[i] = sal_uInt16(i); + std::unique_ptr<sal_uInt16[]> pMetrics = GetTTSimpleGlyphMetrics(pTTFont, + aGlyphIds.data(), + nGlyphs, + bVertical); + if (pMetrics) + { + for (int i = 0; i< nGlyphs; i++) + rWidths[i] = pMetrics[i]; + pMetrics.reset(); + rUnicodeEnc.clear(); + } + + // fill the unicode map + // TODO: isn't this map already available elsewhere in the fontmanager? + sal_uInt32 nCmapSize = 0; + const sal_uInt8* pCmapData = pTTFont->table(O_cmap, nCmapSize); + if (pCmapData) + { + CmapResult aCmapResult; + if (ParseCMAP(pCmapData, nCmapSize, aCmapResult)) + { + FontCharMapRef xFontCharMap(new FontCharMap(aCmapResult)); + for (sal_uInt32 cOld = 0;;) + { + // get next unicode covered by font + const sal_uInt32 c = xFontCharMap->GetNextChar(cOld); + if (c == cOld) + break; + cOld = c; +#if 1 // TODO: remove when sal_Unicode covers all of unicode + if (c > sal_Unicode(~0)) + break; +#endif + // get the matching glyph index + const sal_GlyphId aGlyphId = xFontCharMap->GetGlyphIndex(c); + // update the requested map + rUnicodeEnc[static_cast<sal_Unicode>(c)] = aGlyphId; + } + } + } + } + CloseTTFont(pTTFont); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |