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 | |
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')
-rw-r--r-- | vcl/unx/generic/fontmanager/fontconfig.cxx | 1316 | ||||
-rw-r--r-- | vcl/unx/generic/fontmanager/fontmanager.cxx | 1147 | ||||
-rw-r--r-- | vcl/unx/generic/fontmanager/fontsubst.cxx | 223 | ||||
-rw-r--r-- | vcl/unx/generic/fontmanager/helper.cxx | 262 |
4 files changed, 2948 insertions, 0 deletions
diff --git a/vcl/unx/generic/fontmanager/fontconfig.cxx b/vcl/unx/generic/fontmanager/fontconfig.cxx new file mode 100644 index 000000000..c44cdd2ee --- /dev/null +++ b/vcl/unx/generic/fontmanager/fontconfig.cxx @@ -0,0 +1,1316 @@ +/* -*- 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 . + */ + +#if defined __GNUC__ && !defined __clang_ && __GNUC__ == 10 +// gcc 10.2.0 gets unhappy about one of the OString inside PrintFont at line 656 (while at least a +// recent GCC 12 trunk is happy); +// I have to turn it off here because the warning actually occurs inside rtl::OString +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#endif + +#include <sal/config.h> + +#include <memory> +#include <string_view> + +#include <o3tl/lru_map.hxx> +#include <unx/fontmanager.hxx> +#include <unx/helper.hxx> +#include <comphelper/sequence.hxx> +#include <vcl/svapp.hxx> +#include <vcl/vclenum.hxx> +#include <font/FontSelectPattern.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <i18nutil/unicode.hxx> +#include <rtl/strbuf.hxx> +#include <sal/log.hxx> +#include <tools/diagnose_ex.h> +#include <unicode/uchar.h> +#include <unicode/uscript.h> +#include <officecfg/Office/Common.hxx> +#include <org/freedesktop/PackageKit/SyncDbusSessionHelper.hpp> +#include <config_fonts.h> + +using namespace psp; + +#include <fontconfig/fontconfig.h> + +#include <cstdio> + +#include <unotools/configmgr.hxx> +#include <unotools/syslocaleoptions.hxx> + +#include <osl/process.h> + +#include <o3tl/hash_combine.hxx> +#include <utility> +#include <algorithm> + +using namespace osl; + +namespace +{ + +struct FontOptionsKey +{ + OUString m_sFamilyName; + int m_nFontSize; + FontItalic m_eItalic; + FontWeight m_eWeight; + FontWidth m_eWidth; + FontPitch m_ePitch; + + bool operator==(const FontOptionsKey& rOther) const + { + return m_sFamilyName == rOther.m_sFamilyName && + m_nFontSize == rOther.m_nFontSize && + m_eItalic == rOther.m_eItalic && + m_eWeight == rOther.m_eWeight && + m_eWidth == rOther.m_eWidth && + m_ePitch == rOther.m_ePitch; + } +}; + +} + +namespace std +{ + +template <> struct hash<FontOptionsKey> +{ + std::size_t operator()(const FontOptionsKey& k) const noexcept + { + std::size_t seed = k.m_sFamilyName.hashCode(); + o3tl::hash_combine(seed, k.m_nFontSize); + o3tl::hash_combine(seed, k.m_eItalic); + o3tl::hash_combine(seed, k.m_eWeight); + o3tl::hash_combine(seed, k.m_eWidth); + o3tl::hash_combine(seed, k.m_ePitch); + return seed; + } +}; + +} // end std namespace + +namespace +{ + +struct FcPatternDeleter +{ + void operator()(FcPattern* pPattern) const + { + FcPatternDestroy(pPattern); + } +}; + +typedef std::unique_ptr<FcPattern, FcPatternDeleter> FcPatternUniquePtr; + +class CachedFontConfigFontOptions +{ +private: + o3tl::lru_map<FontOptionsKey, FcPatternUniquePtr> lru_options_cache; + +public: + CachedFontConfigFontOptions() + : lru_options_cache(10) // arbitrary cache size of 10 + { + } + + std::unique_ptr<FontConfigFontOptions> lookup(const FontOptionsKey& rKey) + { + auto it = lru_options_cache.find(rKey); + if (it != lru_options_cache.end()) + return std::make_unique<FontConfigFontOptions>(FcPatternDuplicate(it->second.get())); + return nullptr; + } + + void cache(const FontOptionsKey& rKey, const FcPattern* pPattern) + { + lru_options_cache.insert(std::make_pair(rKey, FcPatternUniquePtr(FcPatternDuplicate(pPattern)))); + } + +}; + +typedef std::pair<FcChar8*, FcChar8*> lang_and_element; + +class FontCfgWrapper +{ + FcFontSet* m_pFontSet; + + void addFontSet( FcSetName ); + + FontCfgWrapper(); + ~FontCfgWrapper(); + +public: + static FontCfgWrapper& get(); + static void release(); + + FcFontSet* getFontSet(); + + void clear(); + +public: + FcResult LocalizedElementFromPattern(FcPattern const * pPattern, FcChar8 **family, + const char *elementtype, const char *elementlangtype); +//to-do, make private and add some cleaner accessor methods + std::unordered_map< OString, OString > m_aFontNameToLocalized; + std::unordered_map< OString, OString > m_aLocalizedToCanonical; + CachedFontConfigFontOptions m_aCachedFontOptions; +private: + void cacheLocalizedFontNames(const FcChar8 *origfontname, const FcChar8 *bestfontname, const std::vector< lang_and_element > &lang_and_elements); + + std::unique_ptr<LanguageTag> m_pLanguageTag; +}; + +} + +FontCfgWrapper::FontCfgWrapper() + : m_pFontSet( nullptr ) +{ + FcInit(); +} + +void FontCfgWrapper::addFontSet( FcSetName eSetName ) +{ + // Add only acceptable fonts to our config, for future fontconfig use. + FcFontSet* pOrig = FcConfigGetFonts( FcConfigGetCurrent(), eSetName ); + if( !pOrig ) + return; + + // filter the font sets to remove obsolete faces + for( int i = 0; i < pOrig->nfont; ++i ) + { + FcPattern* pPattern = pOrig->fonts[i]; + // #i115131# ignore non-scalable fonts + // Scalable fonts are usually outline fonts, but some bitmaps fonts + // (like Noto Color Emoji) are also scalable. + FcBool bScalable = FcFalse; + FcResult eScalableRes = FcPatternGetBool(pPattern, FC_SCALABLE, 0, &bScalable); + if ((eScalableRes != FcResultMatch) || (bScalable == FcFalse)) + continue; + + // Ignore Type 1 fonts, too. + FcChar8* pFormat = nullptr; + FcResult eFormatRes = FcPatternGetString(pPattern, FC_FONTFORMAT, 0, &pFormat); + if ((eFormatRes == FcResultMatch) && (strcmp(reinterpret_cast<char*>(pFormat), "Type 1") == 0)) + continue; + + FcPatternReference( pPattern ); + FcFontSetAdd( m_pFontSet, pPattern ); + } + + // TODO?: FcFontSetDestroy( pOrig ); +} + +namespace +{ + int compareFontNames(const FcPattern *a, const FcPattern *b) + { + FcChar8 *pNameA=nullptr, *pNameB=nullptr; + + bool bHaveA = FcPatternGetString(a, FC_FAMILY, 0, &pNameA) == FcResultMatch; + bool bHaveB = FcPatternGetString(b, FC_FAMILY, 0, &pNameB) == FcResultMatch; + + if (bHaveA && bHaveB) + return strcmp(reinterpret_cast<const char*>(pNameA), reinterpret_cast<const char*>(pNameB)); + + return int(bHaveA) - int(bHaveB); + } + + //Sort fonts so that fonts with the same family name are side-by-side, with + //those with higher version numbers first + class SortFont + { + public: + bool operator()(const FcPattern *a, const FcPattern *b) + { + int comp = compareFontNames(a, b); + if (comp != 0) + return comp < 0; + + int nVersionA=0, nVersionB=0; + + bool bHaveA = FcPatternGetInteger(a, FC_FONTVERSION, 0, &nVersionA) == FcResultMatch; + bool bHaveB = FcPatternGetInteger(b, FC_FONTVERSION, 0, &nVersionB) == FcResultMatch; + + if (bHaveA && bHaveB) + return nVersionA > nVersionB; + + return bHaveA > bHaveB; + } + }; + + //See fdo#30729 for where an old opensymbol installed system-wide can + //clobber the new opensymbol installed locally + + //See if this font is a duplicate with equal attributes which has already been + //inserted, or if it an older version of an inserted fonts. Depends on FcFontSet + //on being sorted with SortFont + bool isPreviouslyDuplicateOrObsoleted(FcFontSet const *pFSet, int i) + { + const FcPattern *a = pFSet->fonts[i]; + + FcPattern* pTestPatternA = FcPatternDuplicate(a); + FcPatternDel(pTestPatternA, FC_FILE); + FcPatternDel(pTestPatternA, FC_CHARSET); + FcPatternDel(pTestPatternA, FC_CAPABILITY); + FcPatternDel(pTestPatternA, FC_FONTVERSION); + FcPatternDel(pTestPatternA, FC_LANG); + + bool bIsDup(false); + + // fdo#66715: loop for case of several font files for same font + for (int j = i - 1; 0 <= j && !bIsDup; --j) + { + const FcPattern *b = pFSet->fonts[j]; + + if (compareFontNames(a, b) != 0) + break; + + FcPattern* pTestPatternB = FcPatternDuplicate(b); + FcPatternDel(pTestPatternB, FC_FILE); + FcPatternDel(pTestPatternB, FC_CHARSET); + FcPatternDel(pTestPatternB, FC_CAPABILITY); + FcPatternDel(pTestPatternB, FC_FONTVERSION); + FcPatternDel(pTestPatternB, FC_LANG); + + bIsDup = FcPatternEqual(pTestPatternA, pTestPatternB); + + FcPatternDestroy(pTestPatternB); + } + + FcPatternDestroy(pTestPatternA); + + return bIsDup; + } +} + +FcFontSet* FontCfgWrapper::getFontSet() +{ + if( !m_pFontSet ) + { + m_pFontSet = FcFontSetCreate(); + bool bRestrictFontSetToApplicationFonts = false; +#if HAVE_MORE_FONTS + bRestrictFontSetToApplicationFonts = getenv("SAL_ABORT_ON_NON_APPLICATION_FONT_USE") != nullptr; +#endif + if (!bRestrictFontSetToApplicationFonts) + addFontSet( FcSetSystem ); + addFontSet( FcSetApplication ); + + std::stable_sort(m_pFontSet->fonts,m_pFontSet->fonts+m_pFontSet->nfont,SortFont()); + } + + return m_pFontSet; +} + +FontCfgWrapper::~FontCfgWrapper() +{ + clear(); + //To-Do: get gtk vclplug smoketest to pass + //FcFini(); +} + +static FontCfgWrapper* pOneInstance = nullptr; + +FontCfgWrapper& FontCfgWrapper::get() +{ + if( ! pOneInstance ) + pOneInstance = new FontCfgWrapper(); + return *pOneInstance; +} + +void FontCfgWrapper::release() +{ + if( pOneInstance ) + { + delete pOneInstance; + pOneInstance = nullptr; + } +} + +namespace +{ + FcChar8* bestname(const std::vector<lang_and_element> &elements, const LanguageTag & rLangTag); + + FcChar8* bestname(const std::vector<lang_and_element> &elements, const LanguageTag & rLangTag) + { + FcChar8* candidate = elements.begin()->second; + /* FIXME-BCP47: once fontconfig supports language tags this + * language-territory stuff needs to be changed! */ + SAL_INFO_IF( !rLangTag.isIsoLocale(), "vcl.fonts", "localizedsorter::bestname - not an ISO locale"); + OString sLangMatch(OUStringToOString(rLangTag.getLanguage().toAsciiLowerCase(), RTL_TEXTENCODING_UTF8)); + OString sFullMatch = sLangMatch + + "-" + + OUStringToOString(rLangTag.getCountry().toAsciiLowerCase(), RTL_TEXTENCODING_UTF8); + + bool alreadyclosematch = false; + bool found_fallback_englishname = false; + for (auto const& element : elements) + { + const char *pLang = reinterpret_cast<const char*>(element.first); + if( sFullMatch == pLang) + { + // both language and country match + candidate = element.second; + break; + } + else if( alreadyclosematch ) + { + // current candidate matches lang of lang-TERRITORY + // override candidate only if there is a full match + continue; + } + else if( sLangMatch == pLang) + { + // just the language matches + candidate = element.second; + alreadyclosematch = true; + } + else if( found_fallback_englishname ) + { + // already found an english fallback, don't override candidate + // unless there is a better language match + continue; + } + else if( rtl_str_compare( pLang, "en") == 0) + { + // select a fallback candidate of the first english element + // name + candidate = element.second; + found_fallback_englishname = true; + } + } + return candidate; + } +} + +//Set up maps to quickly map between a fonts best UI name and all the rest of its names, and vice versa +void FontCfgWrapper::cacheLocalizedFontNames(const FcChar8 *origfontname, const FcChar8 *bestfontname, + const std::vector< lang_and_element > &lang_and_elements) +{ + for (auto const& element : lang_and_elements) + { + const char *candidate = reinterpret_cast<const char*>(element.second); + if (rtl_str_compare(candidate, reinterpret_cast<const char*>(bestfontname)) != 0) + m_aFontNameToLocalized[OString(candidate)] = OString(reinterpret_cast<const char*>(bestfontname)); + } + if (rtl_str_compare(reinterpret_cast<const char*>(origfontname), reinterpret_cast<const char*>(bestfontname)) != 0) + m_aLocalizedToCanonical[OString(reinterpret_cast<const char*>(bestfontname))] = OString(reinterpret_cast<const char*>(origfontname)); +} + +FcResult FontCfgWrapper::LocalizedElementFromPattern(FcPattern const * pPattern, FcChar8 **element, + const char *elementtype, const char *elementlangtype) +{ /* e. g.: ^ FC_FAMILY ^ FC_FAMILYLANG */ + FcChar8 *origelement; + FcResult eElementRes = FcPatternGetString( pPattern, elementtype, 0, &origelement ); + *element = origelement; + + if( eElementRes == FcResultMatch) + { + FcChar8* elementlang = nullptr; + if (FcPatternGetString( pPattern, elementlangtype, 0, &elementlang ) == FcResultMatch) + { + std::vector< lang_and_element > lang_and_elements; + lang_and_elements.emplace_back(elementlang, *element); + int k = 1; + while (true) + { + if (FcPatternGetString( pPattern, elementlangtype, k, &elementlang ) != FcResultMatch) + break; + if (FcPatternGetString( pPattern, elementtype, k, element ) != FcResultMatch) + break; + lang_and_elements.emplace_back(elementlang, *element); + ++k; + } + + if (!m_pLanguageTag) + m_pLanguageTag.reset(new LanguageTag(SvtSysLocaleOptions().GetRealUILanguageTag())); + + *element = bestname(lang_and_elements, *m_pLanguageTag); + + //if this element is a fontname, map the other names to this best-name + if (rtl_str_compare(elementtype, FC_FAMILY) == 0) + cacheLocalizedFontNames(origelement, *element, lang_and_elements); + } + } + + return eElementRes; +} + +void FontCfgWrapper::clear() +{ + m_aFontNameToLocalized.clear(); + m_aLocalizedToCanonical.clear(); + if( m_pFontSet ) + { + FcFontSetDestroy( m_pFontSet ); + m_pFontSet = nullptr; + } + m_pLanguageTag.reset(); +} + +/* + * PrintFontManager::initFontconfig + */ +void PrintFontManager::initFontconfig() +{ + FontCfgWrapper& rWrapper = FontCfgWrapper::get(); + rWrapper.clear(); +} + +namespace +{ + FontWeight convertWeight(int weight) + { + // set weight + if( weight <= FC_WEIGHT_THIN ) + return WEIGHT_THIN; + else if( weight <= FC_WEIGHT_ULTRALIGHT ) + return WEIGHT_ULTRALIGHT; + else if( weight <= FC_WEIGHT_LIGHT ) + return WEIGHT_LIGHT; + else if( weight <= FC_WEIGHT_BOOK ) + return WEIGHT_SEMILIGHT; + else if( weight <= FC_WEIGHT_NORMAL ) + return WEIGHT_NORMAL; + else if( weight <= FC_WEIGHT_MEDIUM ) + return WEIGHT_MEDIUM; + else if( weight <= FC_WEIGHT_SEMIBOLD ) + return WEIGHT_SEMIBOLD; + else if( weight <= FC_WEIGHT_BOLD ) + return WEIGHT_BOLD; + else if( weight <= FC_WEIGHT_ULTRABOLD ) + return WEIGHT_ULTRABOLD; + return WEIGHT_BLACK; + } + + FontItalic convertSlant(int slant) + { + // set italic + if( slant == FC_SLANT_ITALIC ) + return ITALIC_NORMAL; + else if( slant == FC_SLANT_OBLIQUE ) + return ITALIC_OBLIQUE; + return ITALIC_NONE; + } + + FontPitch convertSpacing(int spacing) + { + // set pitch + if( spacing == FC_MONO || spacing == FC_CHARCELL ) + return PITCH_FIXED; + return PITCH_VARIABLE; + } + + // translation: fontconfig enum -> vcl enum + FontWidth convertWidth(int width) + { + if (width == FC_WIDTH_ULTRACONDENSED) + return WIDTH_ULTRA_CONDENSED; + else if (width == FC_WIDTH_EXTRACONDENSED) + return WIDTH_EXTRA_CONDENSED; + else if (width == FC_WIDTH_CONDENSED) + return WIDTH_CONDENSED; + else if (width == FC_WIDTH_SEMICONDENSED) + return WIDTH_SEMI_CONDENSED; + else if (width == FC_WIDTH_SEMIEXPANDED) + return WIDTH_SEMI_EXPANDED; + else if (width == FC_WIDTH_EXPANDED) + return WIDTH_EXPANDED; + else if (width == FC_WIDTH_EXTRAEXPANDED) + return WIDTH_EXTRA_EXPANDED; + else if (width == FC_WIDTH_ULTRAEXPANDED) + return WIDTH_ULTRA_EXPANDED; + return WIDTH_NORMAL; + } +} + +//FontConfig doesn't come with a way to remove an element from a FontSet as far +//as I can see +static void lcl_FcFontSetRemove(FcFontSet* pFSet, int i) +{ + FcPatternDestroy(pFSet->fonts[i]); + + int nTail = pFSet->nfont - (i + 1); + --pFSet->nfont; + if (!nTail) + return; + memmove(pFSet->fonts + i, pFSet->fonts + i + 1, nTail*sizeof(FcPattern*)); +} + +namespace +{ + // for variable fonts, FC_INDEX has been changed such that the lower half is now the + // index of the font within the collection, and the upper half has been repurposed + // as the index within the variations + unsigned int GetCollectionIndex(unsigned int nEntryId) + { + return nEntryId & 0xFFFF; + } + + unsigned int GetVariationIndex(unsigned int nEntryId) + { + return nEntryId >> 16; + } +} + +void PrintFontManager::countFontconfigFonts() +{ + int nFonts = 0; + FontCfgWrapper& rWrapper = FontCfgWrapper::get(); + + FcFontSet* pFSet = rWrapper.getFontSet(); + const bool bMinimalFontset = utl::ConfigManager::IsFuzzing(); + if( pFSet ) + { + SAL_INFO("vcl.fonts", "found " << pFSet->nfont << " entries in fontconfig fontset"); + for( int i = 0; i < pFSet->nfont; i++ ) + { + FcChar8* file = nullptr; + FcChar8* family = nullptr; + FcChar8* style = nullptr; + FcChar8* format = nullptr; + int slant = 0; + int weight = 0; + int width = 0; + int spacing = 0; + int nEntryId = -1; + FcBool scalable = false; + + FcResult eFileRes = FcPatternGetString(pFSet->fonts[i], FC_FILE, 0, &file); + FcResult eFamilyRes = rWrapper.LocalizedElementFromPattern( pFSet->fonts[i], &family, FC_FAMILY, FC_FAMILYLANG ); + if (bMinimalFontset && strncmp(reinterpret_cast<char*>(family), "Liberation", strlen("Liberation"))) + continue; + FcResult eStyleRes = rWrapper.LocalizedElementFromPattern( pFSet->fonts[i], &style, FC_STYLE, FC_STYLELANG ); + FcResult eSlantRes = FcPatternGetInteger(pFSet->fonts[i], FC_SLANT, 0, &slant); + FcResult eWeightRes = FcPatternGetInteger(pFSet->fonts[i], FC_WEIGHT, 0, &weight); + FcResult eWidthRes = FcPatternGetInteger(pFSet->fonts[i], FC_WIDTH, 0, &width); + FcResult eSpacRes = FcPatternGetInteger(pFSet->fonts[i], FC_SPACING, 0, &spacing); + FcResult eScalableRes = FcPatternGetBool(pFSet->fonts[i], FC_SCALABLE, 0, &scalable); + FcResult eIndexRes = FcPatternGetInteger(pFSet->fonts[i], FC_INDEX, 0, &nEntryId); + FcResult eFormatRes = FcPatternGetString(pFSet->fonts[i], FC_FONTFORMAT, 0, &format); + + if( eFileRes != FcResultMatch || eFamilyRes != FcResultMatch || eScalableRes != FcResultMatch ) + continue; + + SAL_INFO( + "vcl.fonts.detail", + "found font \"" << family << "\" in file " << file << ", weight = " + << (eWeightRes == FcResultMatch ? weight : -1) << ", slant = " + << (eSpacRes == FcResultMatch ? slant : -1) << ", style = \"" + << (eStyleRes == FcResultMatch ? reinterpret_cast<const char*>(style) : "<nil>") + << "\", width = " << (eWeightRes == FcResultMatch ? width : -1) << ", spacing = " + << (eSpacRes == FcResultMatch ? spacing : -1) << ", scalable = " + << (eScalableRes == FcResultMatch ? scalable : -1) << ", format " + << (eFormatRes == FcResultMatch + ? reinterpret_cast<const char*>(format) : "<unknown>")); + +// OSL_ASSERT(eScalableRes != FcResultMatch || scalable); + + // only scalable fonts are usable to psprint anyway + if( eScalableRes == FcResultMatch && ! scalable ) + continue; + + if (isPreviouslyDuplicateOrObsoleted(pFSet, i)) + { + SAL_INFO("vcl.fonts.detail", "Ditching " << file << " as duplicate/obsolete"); + continue; + } + + // see if this font is already cached + // update attributes + OString aDir, aBase, aOrgPath( reinterpret_cast<char*>(file) ); + splitPath( aOrgPath, aDir, aBase ); + + int nDirID = getDirectoryAtom( aDir ); + SAL_INFO("vcl.fonts.detail", "file " << aBase << " not cached"); + // not known, analyze font file to get attributes + // not described by fontconfig (e.g. alias names, PSName) + if (eFormatRes != FcResultMatch) + format = nullptr; + std::vector<PrintFont> aFonts = analyzeFontFile( nDirID, aBase, reinterpret_cast<char*>(format) ); + if(aFonts.empty()) + { + SAL_INFO( + "vcl.fonts", "Warning: file \"" << aOrgPath << "\" is unusable to psprint"); + //remove font, reuse index + //we want to remove unusable fonts here, in case there is a usable font + //which duplicates the properties of the unusable one + + //not removing the unusable font will risk the usable font being rejected + //as a duplicate by isPreviouslyDuplicateOrObsoleted + lcl_FcFontSetRemove(pFSet, i--); + continue; + } + + std::optional<PrintFont> xUpdate; + + if (aFonts.size() == 1) // one font + xUpdate = aFonts.front(); + else // more than one font + { + // a collection entry, get the correct index + if( eIndexRes == FcResultMatch && nEntryId != -1 ) + { + int nCollectionEntry = GetCollectionIndex(nEntryId); + for (const auto & font : aFonts) + { + if( font.m_nCollectionEntry == nCollectionEntry ) + { + xUpdate = font; + break; + } + } + } + + if (xUpdate) + { + // update collection entry + // additional entries will be created in the cache + // if this is a new index (that is if the loop above + // ran to the end of the list) + xUpdate->m_nCollectionEntry = GetCollectionIndex(nEntryId); + } + else + { + SAL_INFO( + "vcl.fonts", + "multiple fonts for file, but no index in fontconfig pattern ! (index res =" + << eIndexRes << " collection entry = " << nEntryId + << "; file will not be used"); + // we have found more than one font in this file + // but fontconfig will not tell us which index is meant + // -> something is in disorder, do not use this font + } + } + + if (xUpdate) + { + // set family name + if( eWeightRes == FcResultMatch ) + xUpdate->m_eWeight = convertWeight(weight); + if( eWidthRes == FcResultMatch ) + xUpdate->m_eWidth = convertWidth(width); + if( eSpacRes == FcResultMatch ) + xUpdate->m_ePitch = convertSpacing(spacing); + if( eSlantRes == FcResultMatch ) + xUpdate->m_eItalic = convertSlant(slant); + if( eStyleRes == FcResultMatch ) + xUpdate->m_aStyleName = OStringToOUString( std::string_view( reinterpret_cast<char*>(style) ), RTL_TEXTENCODING_UTF8 ); + if( eIndexRes == FcResultMatch ) + xUpdate->m_nVariationEntry = GetVariationIndex(nEntryId); + + // sort into known fonts + fontID aFont = m_nNextFontID++; + m_aFonts.emplace( aFont, *xUpdate ); + m_aFontFileToFontID[ aBase ].insert( aFont ); + nFonts++; + SAL_INFO("vcl.fonts.detail", "inserted font " << family << " as fontID " << aFont); + } + } + } + + // how does one get rid of the config ? + SAL_INFO("vcl.fonts", "inserted " << nFonts << " fonts from fontconfig"); +} + +void PrintFontManager::deinitFontconfig() +{ + FontCfgWrapper::release(); +} + +void PrintFontManager::addFontconfigDir( const OString& rDirName ) +{ + const char* pDirName = rDirName.getStr(); + bool bDirOk = (FcConfigAppFontAddDir(FcConfigGetCurrent(), reinterpret_cast<FcChar8 const *>(pDirName) ) == FcTrue); + + SAL_INFO("vcl.fonts", "FcConfigAppFontAddDir( \"" << pDirName << "\") => " << bDirOk); + + if( !bDirOk ) + return; + + // load dir-specific fc-config file too if available + const OString aConfFileName = rDirName + "/fc_local.conf"; + FILE* pCfgFile = fopen( aConfFileName.getStr(), "rb" ); + if( pCfgFile ) + { + fclose( pCfgFile); + bool bCfgOk = FcConfigParseAndLoad(FcConfigGetCurrent(), + reinterpret_cast<FcChar8 const *>(aConfFileName.getStr()), FcTrue); + + SAL_INFO_IF(!bCfgOk, + "vcl.fonts", "FcConfigParseAndLoad( \"" + << aConfFileName << "\") => " << bCfgOk); + } else { + SAL_INFO("vcl.fonts", "cannot open " << aConfFileName); + } +} + +static void addtopattern(FcPattern *pPattern, + FontItalic eItalic, FontWeight eWeight, FontWidth eWidth, FontPitch ePitch) +{ + if( eItalic != ITALIC_DONTKNOW ) + { + int nSlant = FC_SLANT_ROMAN; + switch( eItalic ) + { + case ITALIC_NORMAL: + nSlant = FC_SLANT_ITALIC; + break; + case ITALIC_OBLIQUE: + nSlant = FC_SLANT_OBLIQUE; + break; + default: + break; + } + FcPatternAddInteger(pPattern, FC_SLANT, nSlant); + } + if( eWeight != WEIGHT_DONTKNOW ) + { + int nWeight = FC_WEIGHT_NORMAL; + switch( eWeight ) + { + case WEIGHT_THIN: nWeight = FC_WEIGHT_THIN;break; + case WEIGHT_ULTRALIGHT: nWeight = FC_WEIGHT_ULTRALIGHT;break; + case WEIGHT_LIGHT: nWeight = FC_WEIGHT_LIGHT;break; + case WEIGHT_SEMILIGHT: nWeight = FC_WEIGHT_BOOK;break; + case WEIGHT_NORMAL: nWeight = FC_WEIGHT_NORMAL;break; + case WEIGHT_MEDIUM: nWeight = FC_WEIGHT_MEDIUM;break; + case WEIGHT_SEMIBOLD: nWeight = FC_WEIGHT_SEMIBOLD;break; + case WEIGHT_BOLD: nWeight = FC_WEIGHT_BOLD;break; + case WEIGHT_ULTRABOLD: nWeight = FC_WEIGHT_ULTRABOLD;break; + case WEIGHT_BLACK: nWeight = FC_WEIGHT_BLACK;break; + default: + break; + } + FcPatternAddInteger(pPattern, FC_WEIGHT, nWeight); + } + if( eWidth != WIDTH_DONTKNOW ) + { + int nWidth = FC_WIDTH_NORMAL; + switch( eWidth ) + { + case WIDTH_ULTRA_CONDENSED: nWidth = FC_WIDTH_ULTRACONDENSED;break; + case WIDTH_EXTRA_CONDENSED: nWidth = FC_WIDTH_EXTRACONDENSED;break; + case WIDTH_CONDENSED: nWidth = FC_WIDTH_CONDENSED;break; + case WIDTH_SEMI_CONDENSED: nWidth = FC_WIDTH_SEMICONDENSED;break; + case WIDTH_NORMAL: nWidth = FC_WIDTH_NORMAL;break; + case WIDTH_SEMI_EXPANDED: nWidth = FC_WIDTH_SEMIEXPANDED;break; + case WIDTH_EXPANDED: nWidth = FC_WIDTH_EXPANDED;break; + case WIDTH_EXTRA_EXPANDED: nWidth = FC_WIDTH_EXTRAEXPANDED;break; + case WIDTH_ULTRA_EXPANDED: nWidth = FC_WIDTH_ULTRAEXPANDED;break; + default: + break; + } + FcPatternAddInteger(pPattern, FC_WIDTH, nWidth); + } + if( ePitch == PITCH_DONTKNOW ) + return; + + int nSpacing = FC_PROPORTIONAL; + switch( ePitch ) + { + case PITCH_FIXED: nSpacing = FC_MONO;break; + case PITCH_VARIABLE: nSpacing = FC_PROPORTIONAL;break; + default: + break; + } + FcPatternAddInteger(pPattern, FC_SPACING, nSpacing); + if (nSpacing == FC_MONO) + FcPatternAddString(pPattern, FC_FAMILY, reinterpret_cast<FcChar8 const *>("monospace")); +} + +namespace +{ + //Someday fontconfig will hopefully use bcp47, see fdo#19869 + //In the meantime try something that will fit to workaround fdo#35118 + OString mapToFontConfigLangTag(const LanguageTag &rLangTag) + { +#if defined(FC_VERSION) && (FC_VERSION >= 20492) + std::shared_ptr<FcStrSet> xLangSet(FcGetLangs(), FcStrSetDestroy); + OString sLangAttrib; + + sLangAttrib = OUStringToOString(rLangTag.getBcp47(), RTL_TEXTENCODING_UTF8).toAsciiLowerCase(); + if (FcStrSetMember(xLangSet.get(), reinterpret_cast<const FcChar8*>(sLangAttrib.getStr()))) + { + return sLangAttrib; + } + + sLangAttrib = OUStringToOString(rLangTag.getLanguageAndScript(), RTL_TEXTENCODING_UTF8).toAsciiLowerCase(); + if (FcStrSetMember(xLangSet.get(), reinterpret_cast<const FcChar8*>(sLangAttrib.getStr()))) + { + return sLangAttrib; + } + + OString sLang = OUStringToOString(rLangTag.getLanguage(), RTL_TEXTENCODING_UTF8).toAsciiLowerCase(); + OString sRegion = OUStringToOString(rLangTag.getCountry(), RTL_TEXTENCODING_UTF8).toAsciiLowerCase(); + + if (!sRegion.isEmpty()) + { + sLangAttrib = sLang + "-" + sRegion; + if (FcStrSetMember(xLangSet.get(), reinterpret_cast<const FcChar8*>(sLangAttrib.getStr()))) + { + return sLangAttrib; + } + } + + if (FcStrSetMember(xLangSet.get(), reinterpret_cast<const FcChar8*>(sLang.getStr()))) + { + return sLang; + } + + return OString(); +#else + OString sLangAttrib = OUStringToOString(rLangTag.getLanguageAndScript(), RTL_TEXTENCODING_UTF8).toAsciiLowerCase(); + if (sLangAttrib.equalsIgnoreAsciiCase("pa-in")) + sLangAttrib = "pa"; + return sLangAttrib; +#endif + } + + bool isEmoji(sal_uInt32 nCurrentChar) + { +#if U_ICU_VERSION_MAJOR_NUM >= 57 + return u_hasBinaryProperty(nCurrentChar, UCHAR_EMOJI); +#else + return false; +#endif + } + + //returns true if the given code-point couldn't possibly be in rLangTag. + bool isImpossibleCodePointForLang(const LanguageTag &rLangTag, sal_uInt32 currentChar) + { + //a non-default script is set, lets believe it + if (rLangTag.hasScript()) + return false; + + int32_t script = u_getIntPropertyValue(currentChar, UCHAR_SCRIPT); + UScriptCode eScript = static_cast<UScriptCode>(script); + bool bIsImpossible = false; + OUString sLang = rLangTag.getLanguage(); + switch (eScript) + { + //http://en.wiktionary.org/wiki/Category:Oriya_script_languages + case USCRIPT_ORIYA: + bIsImpossible = + sLang != "or" && + sLang != "kxv"; + break; + //http://en.wiktionary.org/wiki/Category:Telugu_script_languages + case USCRIPT_TELUGU: + bIsImpossible = + sLang != "te" && + sLang != "gon" && + sLang != "kfc"; + break; + //http://en.wiktionary.org/wiki/Category:Bengali_script_languages + case USCRIPT_BENGALI: + bIsImpossible = + sLang != "bn" && + sLang != "as" && + sLang != "bpy" && + sLang != "ctg" && + sLang != "sa"; + break; + default: + break; + } + SAL_WARN_IF(bIsImpossible, "vcl.fonts", "In glyph fallback throwing away the language property of " + << sLang << " because the detected script for '0x" + << OUString::number(currentChar, 16) + << "' is " << uscript_getName(eScript) + << " and that language doesn't make sense. Autodetecting instead."); + return bIsImpossible; + } + + OUString getExemplarLangTagForCodePoint(sal_uInt32 currentChar) + { + if (isEmoji(currentChar)) + return "und-zsye"; + int32_t script = u_getIntPropertyValue(currentChar, UCHAR_SCRIPT); + UScriptCode eScript = static_cast<UScriptCode>(script); + OStringBuffer aBuf(unicode::getExemplarLanguageForUScriptCode(eScript)); + if (const char* pScriptCode = uscript_getShortName(eScript)) + aBuf.append('-').append(pScriptCode); + return OStringToOUString(aBuf, RTL_TEXTENCODING_UTF8); + } +} + +IMPL_LINK_NOARG(PrintFontManager, autoInstallFontLangSupport, Timer *, void) +{ + try + { + using namespace org::freedesktop::PackageKit; + css::uno::Reference<XSyncDbusSessionHelper> xSyncDbusSessionHelper(SyncDbusSessionHelper::create(comphelper::getProcessComponentContext())); + xSyncDbusSessionHelper->InstallFontconfigResources(comphelper::containerToSequence(m_aCurrentRequests), "hide-finished"); + } + catch (const css::uno::Exception&) + { + TOOLS_INFO_EXCEPTION("vcl.fonts", "InstallFontconfigResources problem"); + // Disable this method from now on. It's simply not available on some systems + // and leads to an error dialog being shown each time this is called tdf#104883 + std::shared_ptr<comphelper::ConfigurationChanges> batch( comphelper::ConfigurationChanges::create() ); + officecfg::Office::Common::PackageKit::EnableFontInstallation::set(false, batch); + batch->commit(); + } + + m_aCurrentRequests.clear(); +} + +void PrintFontManager::Substitute(vcl::font::FontSelectPattern &rPattern, OUString& rMissingCodes) +{ + FontCfgWrapper& rWrapper = FontCfgWrapper::get(); + + // build pattern argument for fontconfig query + FcPattern* pPattern = FcPatternCreate(); + + // Prefer scalable fonts + FcPatternAddBool(pPattern, FC_SCALABLE, FcTrue); + + const OString aTargetName = OUStringToOString( rPattern.maTargetName, RTL_TEXTENCODING_UTF8 ); + const FcChar8* pTargetNameUtf8 = reinterpret_cast<FcChar8 const *>(aTargetName.getStr()); + FcPatternAddString(pPattern, FC_FAMILY, pTargetNameUtf8); + + LanguageTag aLangTag(rPattern.meLanguage); + OString aLangAttrib = mapToFontConfigLangTag(aLangTag); + + bool bMissingJustBullet = false; + + // Add required Unicode characters, if any + if ( !rMissingCodes.isEmpty() ) + { + FcCharSet *codePoints = FcCharSetCreate(); + bMissingJustBullet = rMissingCodes.getLength() == 1 && rMissingCodes[0] == 0xb7; + for( sal_Int32 nStrIndex = 0; nStrIndex < rMissingCodes.getLength(); ) + { + // also handle unicode surrogates + const sal_uInt32 nCode = rMissingCodes.iterateCodePoints( &nStrIndex ); + FcCharSetAddChar( codePoints, nCode ); + //if the codepoint is impossible for this lang tag, then clear it + //and autodetect something useful + if (!aLangAttrib.isEmpty() && (isImpossibleCodePointForLang(aLangTag, nCode) || isEmoji(nCode))) + aLangAttrib.clear(); + //#i105784#/rhbz#527719 improve selection of fallback font + if (aLangAttrib.isEmpty()) + { + aLangTag.reset(getExemplarLangTagForCodePoint(nCode)); + aLangAttrib = mapToFontConfigLangTag(aLangTag); + } + } + FcPatternAddCharSet(pPattern, FC_CHARSET, codePoints); + FcCharSetDestroy(codePoints); + } + + if (!aLangAttrib.isEmpty()) + FcPatternAddString(pPattern, FC_LANG, reinterpret_cast<FcChar8 const *>(aLangAttrib.getStr())); + + addtopattern(pPattern, rPattern.GetItalic(), rPattern.GetWeight(), + rPattern.GetWidthType(), rPattern.GetPitch()); + + // query fontconfig for a substitute + FcConfigSubstitute(FcConfigGetCurrent(), pPattern, FcMatchPattern); + FcDefaultSubstitute(pPattern); + + // process the result of the fontconfig query + FcResult eResult = FcResultNoMatch; + FcFontSet* pFontSet = rWrapper.getFontSet(); + FcPattern* pResult = FcFontSetMatch(FcConfigGetCurrent(), &pFontSet, 1, pPattern, &eResult); + FcPatternDestroy( pPattern ); + + FcFontSet* pSet = nullptr; + if( pResult ) + { + pSet = FcFontSetCreate(); + // info: destroying the pSet destroys pResult implicitly + // since pResult was "added" to pSet + FcFontSetAdd( pSet, pResult ); + } + + if( pSet ) + { + if( pSet->nfont > 0 ) + { + bool bRet = false; + + //extract the closest match + FcChar8* file = nullptr; + FcResult eFileRes = FcPatternGetString(pSet->fonts[0], FC_FILE, 0, &file); + int nEntryId = 0; + FcResult eIndexRes = FcPatternGetInteger(pSet->fonts[0], FC_INDEX, 0, &nEntryId); + if (eIndexRes != FcResultMatch) + nEntryId = 0; + if( eFileRes == FcResultMatch ) + { + OString aDir, aBase, aOrgPath( reinterpret_cast<char*>(file) ); + splitPath( aOrgPath, aDir, aBase ); + int nDirID = getDirectoryAtom( aDir ); + fontID aFont = findFontFileID(nDirID, aBase, GetCollectionIndex(nEntryId), GetVariationIndex(nEntryId)); + if( aFont > 0 ) + { + FastPrintFontInfo aInfo; + bRet = getFontFastInfo( aFont, aInfo ); + rPattern.maSearchName = aInfo.m_aFamilyName; + } + } + + SAL_WARN_IF(!bRet, "vcl.fonts", "no FC_FILE found, falling back to name search"); + + if (!bRet) + { + FcChar8* family = nullptr; + FcResult eFamilyRes = FcPatternGetString( pSet->fonts[0], FC_FAMILY, 0, &family ); + + // get the family name + if( eFamilyRes == FcResultMatch ) + { + OString sFamily(reinterpret_cast<char*>(family)); + std::unordered_map< OString, OString >::const_iterator aI = + rWrapper.m_aFontNameToLocalized.find(sFamily); + if (aI != rWrapper.m_aFontNameToLocalized.end()) + sFamily = aI->second; + rPattern.maSearchName = OStringToOUString( sFamily, RTL_TEXTENCODING_UTF8 ); + bRet = true; + } + } + + if (bRet) + { + int val = 0; + if (FcResultMatch == FcPatternGetInteger(pSet->fonts[0], FC_WEIGHT, 0, &val)) + rPattern.SetWeight( convertWeight(val) ); + if (FcResultMatch == FcPatternGetInteger(pSet->fonts[0], FC_SLANT, 0, &val)) + rPattern.SetItalic( convertSlant(val) ); + if (FcResultMatch == FcPatternGetInteger(pSet->fonts[0], FC_SPACING, 0, &val)) + rPattern.SetPitch ( convertSpacing(val) ); + if (FcResultMatch == FcPatternGetInteger(pSet->fonts[0], FC_WIDTH, 0, &val)) + rPattern.SetWidthType ( convertWidth(val) ); + FcBool bEmbolden; + if (FcResultMatch == FcPatternGetBool(pSet->fonts[0], FC_EMBOLDEN, 0, &bEmbolden)) + rPattern.mbEmbolden = bEmbolden; + FcMatrix *pMatrix = nullptr; + if (FcResultMatch == FcPatternGetMatrix(pSet->fonts[0], FC_MATRIX, 0, &pMatrix)) + { + rPattern.maItalicMatrix.xx = pMatrix->xx; + rPattern.maItalicMatrix.xy = pMatrix->xy; + rPattern.maItalicMatrix.yx = pMatrix->yx; + rPattern.maItalicMatrix.yy = pMatrix->yy; + } + } + + // update rMissingCodes by removing resolved code points + if( !rMissingCodes.isEmpty() ) + { + std::unique_ptr<sal_uInt32[]> const pRemainingCodes(new sal_uInt32[rMissingCodes.getLength()]); + int nRemainingLen = 0; + FcCharSet* codePoints; + if (!FcPatternGetCharSet(pSet->fonts[0], FC_CHARSET, 0, &codePoints)) + { + for( sal_Int32 nStrIndex = 0; nStrIndex < rMissingCodes.getLength(); ) + { + // also handle surrogates + const sal_uInt32 nCode = rMissingCodes.iterateCodePoints( &nStrIndex ); + if (FcCharSetHasChar(codePoints, nCode) != FcTrue) + pRemainingCodes[ nRemainingLen++ ] = nCode; + } + } + OUString sStillMissing(pRemainingCodes.get(), nRemainingLen); + if (!Application::IsHeadlessModeEnabled() && officecfg::Office::Common::PackageKit::EnableFontInstallation::get()) + { + if (sStillMissing == rMissingCodes) //replaced nothing + { + //It'd be better if we could ask packagekit using the + //missing codepoints or some such rather than using + //"language" as a proxy to how fontconfig considers + //scripts to default to a given language. + for (sal_Int32 i = 0; i < nRemainingLen; ++i) + { + LanguageTag aOurTag(getExemplarLangTagForCodePoint(pRemainingCodes[i])); + OString sTag = OUStringToOString(aOurTag.getBcp47(), RTL_TEXTENCODING_UTF8); + if (!m_aPreviousLangSupportRequests.insert(sTag).second) + continue; + sTag = mapToFontConfigLangTag(aOurTag); + if (!sTag.isEmpty() && m_aPreviousLangSupportRequests.find(sTag) == m_aPreviousLangSupportRequests.end()) + { + OString sReq = OString::Concat(":lang=") + sTag; + m_aCurrentRequests.push_back(OUString::fromUtf8(sReq)); + m_aPreviousLangSupportRequests.insert(sTag); + } + } + } + if (!m_aCurrentRequests.empty()) + m_aFontInstallerTimer.Start(); + } + rMissingCodes = sStillMissing; + } + } + + FcFontSetDestroy( pSet ); + } + + SAL_INFO("vcl.fonts", "PrintFontManager::Substitute: replacing missing font: '" + << rPattern.maTargetName << "' with '" << rPattern.maSearchName + << "'"); + + static bool bAbortOnFontSubstitute = getenv("SAL_ABORT_ON_NON_APPLICATION_FONT_USE") != nullptr; + if (bAbortOnFontSubstitute && rPattern.maTargetName != rPattern.maSearchName) + { + if (bMissingJustBullet) + { + assert(rPattern.maTargetName == "Amiri Quran" || rPattern.maTargetName == "David CLM" || + rPattern.maTargetName == "EmojiOne Color" || rPattern.maTargetName == "Frank Ruehl CLM" || + rPattern.maTargetName == "KacstBook" || rPattern.maTargetName == "KacstOffice"); + // These fonts exist in "more_fonts", but have no U+00B7 MIDDLE DOT + // so will always glyph fallback on measuring mnBulletOffset in + // ImplFontMetricData::ImplInitTextLineSize + return; + } + if (rPattern.maTargetName == "Linux Libertine G" && rPattern.maSearchName == "Linux Libertine O") + return; + SAL_WARN("vcl.fonts", "PrintFontManager::Substitute: missing font: '" << rPattern.maTargetName << + "' try: " << rPattern.maSearchName << " instead"); + std::abort(); + } +} + +FontConfigFontOptions::~FontConfigFontOptions() +{ + FcPatternDestroy(mpPattern); +} + +FcPattern *FontConfigFontOptions::GetPattern() const +{ + return mpPattern; +} + +void FontConfigFontOptions::SyncPattern(const OString& rFileName, sal_uInt32 nIndex, sal_uInt32 nVariation, bool bEmbolden) +{ + FcPatternDel(mpPattern, FC_FILE); + FcPatternAddString(mpPattern, FC_FILE, reinterpret_cast<FcChar8 const *>(rFileName.getStr())); + FcPatternDel(mpPattern, FC_INDEX); + sal_uInt32 nFcIndex = (nVariation << 16) | nIndex; + FcPatternAddInteger(mpPattern, FC_INDEX, nFcIndex); + FcPatternDel(mpPattern, FC_EMBOLDEN); + FcPatternAddBool(mpPattern, FC_EMBOLDEN, bEmbolden ? FcTrue : FcFalse); +} + +std::unique_ptr<FontConfigFontOptions> PrintFontManager::getFontOptions(const FontAttributes& rInfo, int nSize) +{ + FontOptionsKey aKey{ rInfo.GetFamilyName(), nSize, rInfo.GetItalic(), + rInfo.GetWeight(), rInfo.GetWidthType(), rInfo.GetPitch() }; + + FontCfgWrapper& rWrapper = FontCfgWrapper::get(); + + std::unique_ptr<FontConfigFontOptions> pOptions = rWrapper.m_aCachedFontOptions.lookup(aKey); + if (pOptions) + return pOptions; + + FcConfig* pConfig = FcConfigGetCurrent(); + FcPattern* pPattern = FcPatternCreate(); + + OString sFamily = OUStringToOString(aKey.m_sFamilyName, RTL_TEXTENCODING_UTF8); + + std::unordered_map< OString, OString >::const_iterator aI = rWrapper.m_aLocalizedToCanonical.find(sFamily); + if (aI != rWrapper.m_aLocalizedToCanonical.end()) + sFamily = aI->second; + if( !sFamily.isEmpty() ) + FcPatternAddString(pPattern, FC_FAMILY, reinterpret_cast<FcChar8 const *>(sFamily.getStr())); + + addtopattern(pPattern, aKey.m_eItalic, aKey.m_eWeight, aKey.m_eWidth, aKey.m_ePitch); + FcPatternAddDouble(pPattern, FC_PIXEL_SIZE, nSize); + + FcConfigSubstitute(pConfig, pPattern, FcMatchPattern); + FontConfigFontOptions::cairo_font_options_substitute(pPattern); + FcDefaultSubstitute(pPattern); + + FcResult eResult = FcResultNoMatch; + FcFontSet* pFontSet = rWrapper.getFontSet(); + if (FcPattern* pResult = FcFontSetMatch(pConfig, &pFontSet, 1, pPattern, &eResult)) + { + rWrapper.m_aCachedFontOptions.cache(aKey, pResult); + pOptions.reset(new FontConfigFontOptions(pResult)); + } + + // cleanup + FcPatternDestroy( pPattern ); + + return pOptions; +} + + +void PrintFontManager::matchFont( FastPrintFontInfo& rInfo, const css::lang::Locale& rLocale ) +{ + FontCfgWrapper& rWrapper = FontCfgWrapper::get(); + + FcConfig* pConfig = FcConfigGetCurrent(); + FcPattern* pPattern = FcPatternCreate(); + + // populate pattern with font characteristics + const LanguageTag aLangTag(rLocale); + const OString aLangAttrib = mapToFontConfigLangTag(aLangTag); + if (!aLangAttrib.isEmpty()) + FcPatternAddString(pPattern, FC_LANG, reinterpret_cast<FcChar8 const *>(aLangAttrib.getStr())); + + OString aFamily = OUStringToOString( rInfo.m_aFamilyName, RTL_TEXTENCODING_UTF8 ); + if( !aFamily.isEmpty() ) + FcPatternAddString(pPattern, FC_FAMILY, reinterpret_cast<FcChar8 const *>(aFamily.getStr())); + + addtopattern(pPattern, rInfo.m_eItalic, rInfo.m_eWeight, rInfo.m_eWidth, rInfo.m_ePitch); + + FcConfigSubstitute(pConfig, pPattern, FcMatchPattern); + FcDefaultSubstitute(pPattern); + FcResult eResult = FcResultNoMatch; + FcFontSet *pFontSet = rWrapper.getFontSet(); + FcPattern* pResult = FcFontSetMatch(pConfig, &pFontSet, 1, pPattern, &eResult); + if( pResult ) + { + FcFontSet* pSet = FcFontSetCreate(); + FcFontSetAdd( pSet, pResult ); + if( pSet->nfont > 0 ) + { + //extract the closest match + FcChar8* file = nullptr; + FcResult eFileRes = FcPatternGetString(pSet->fonts[0], FC_FILE, 0, &file); + int nEntryId = 0; + FcResult eIndexRes = FcPatternGetInteger(pSet->fonts[0], FC_INDEX, 0, &nEntryId); + if (eIndexRes != FcResultMatch) + nEntryId = 0; + if( eFileRes == FcResultMatch ) + { + OString aDir, aBase, aOrgPath( reinterpret_cast<char*>(file) ); + splitPath( aOrgPath, aDir, aBase ); + int nDirID = getDirectoryAtom( aDir ); + fontID aFont = findFontFileID(nDirID, aBase, + GetCollectionIndex(nEntryId), + GetVariationIndex(nEntryId)); + if( aFont > 0 ) + getFontFastInfo( aFont, rInfo ); + } + } + // info: destroying the pSet destroys pResult implicitly + // since pResult was "added" to pSet + FcFontSetDestroy( pSet ); + } + + // cleanup + FcPatternDestroy( pPattern ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ 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: */ diff --git a/vcl/unx/generic/fontmanager/fontsubst.cxx b/vcl/unx/generic/fontmanager/fontsubst.cxx new file mode 100644 index 000000000..afbe53dd4 --- /dev/null +++ b/vcl/unx/generic/fontmanager/fontsubst.cxx @@ -0,0 +1,223 @@ +/* -*- 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 <sal/config.h> + +#include <unx/geninst.h> +#include <font/PhysicalFontCollection.hxx> +#include <font/fontsubstitution.hxx> +#include <unx/fontmanager.hxx> + +// platform specific font substitution hooks + +namespace { + +class FcPreMatchSubstitution +: public vcl::font::PreMatchFontSubstitution +{ +public: + bool FindFontSubstitute( vcl::font::FontSelectPattern& ) const override; + typedef ::std::pair<vcl::font::FontSelectPattern, vcl::font::FontSelectPattern> value_type; +private: + typedef ::std::list<value_type> CachedFontMapType; + mutable CachedFontMapType maCachedFontMap; +}; + +class FcGlyphFallbackSubstitution +: public vcl::font::GlyphFallbackFontSubstitution +{ + // TODO: add a cache +public: + bool FindFontSubstitute(vcl::font::FontSelectPattern&, LogicalFontInstance* pLogicalFont, OUString& rMissingCodes) const override; +}; + +} + +void SalGenericInstance::RegisterFontSubstitutors(vcl::font::PhysicalFontCollection* pFontCollection) +{ + // register font fallback substitutions + static FcPreMatchSubstitution aSubstPreMatch; + pFontCollection->SetPreMatchHook( &aSubstPreMatch ); + + // register glyph fallback substitutions + static FcGlyphFallbackSubstitution aSubstFallback; + pFontCollection->SetFallbackHook( &aSubstFallback ); +} + +static vcl::font::FontSelectPattern GetFcSubstitute(const vcl::font::FontSelectPattern &rFontSelData, OUString& rMissingCodes) +{ + vcl::font::FontSelectPattern aSubstituted(rFontSelData); + psp::PrintFontManager& rMgr = psp::PrintFontManager::get(); + rMgr.Substitute(aSubstituted, rMissingCodes); + return aSubstituted; +} + +namespace +{ + bool uselessmatch(const vcl::font::FontSelectPattern &rOrig, const vcl::font::FontSelectPattern &rNew) + { + return + ( + rOrig.maTargetName == rNew.maSearchName && + rOrig.GetWeight() == rNew.GetWeight() && + rOrig.GetItalic() == rNew.GetItalic() && + rOrig.GetPitch() == rNew.GetPitch() && + rOrig.GetWidthType() == rNew.GetWidthType() + ); + } + + class equal + { + private: + const vcl::font::FontSelectPattern& mrAttributes; + public: + explicit equal(const vcl::font::FontSelectPattern& rAttributes) + : mrAttributes(rAttributes) + { + } + bool operator()(const FcPreMatchSubstitution::value_type& rOther) const + { return rOther.first == mrAttributes; } + }; +} + +bool FcPreMatchSubstitution::FindFontSubstitute(vcl::font::FontSelectPattern &rFontSelData) const +{ + // We don't actually want to talk to Fontconfig at all for symbol fonts + if( rFontSelData.IsSymbolFont() ) + return false; + // StarSymbol is a unicode font, but it still deserves the symbol flag + if ( IsStarSymbol(rFontSelData.maSearchName) ) + return false; + + //see fdo#41556 and fdo#47636 + //fontconfig can return e.g. an italic font for a non-italic input and/or + //different fonts depending on fontsize, bold, etc settings so don't cache + //just on the name, cache map all the input and all the output not just map + //from original selection to output fontname + vcl::font::FontSelectPattern& rPatternAttributes = rFontSelData; + CachedFontMapType &rCachedFontMap = maCachedFontMap; + CachedFontMapType::iterator itr = std::find_if(rCachedFontMap.begin(), rCachedFontMap.end(), equal(rPatternAttributes)); + if (itr != rCachedFontMap.end()) + { + // Cached substitution + rFontSelData = itr->second; + if (itr != rCachedFontMap.begin()) + { + // MRU, move it to the front + rCachedFontMap.splice(rCachedFontMap.begin(), rCachedFontMap, itr); + } + return true; + } + + OUString aDummy; + const vcl::font::FontSelectPattern aOut = GetFcSubstitute( rFontSelData, aDummy ); + + if( aOut.maSearchName.isEmpty() ) + return false; + + const bool bHaveSubstitute = !uselessmatch( rFontSelData, aOut ); + +#ifdef DEBUG + std::ostringstream oss; + oss << "FcPreMatchSubstitution \"" + << rFontSelData.maTargetName + << "\" bipw=" + << rFontSelData.GetWeight() + << rFontSelData.GetItalic() + << rFontSelData.GetPitch() + << rFontSelData.GetWidthType() + << " -> "; + if( !bHaveSubstitute ) + oss << "no substitute available."; + else + oss << "\"" + << aOut.maSearchName + << "\" bipw=" + << aOut.GetWeight() + << aOut.GetItalic() + << aOut.GetPitch() + << aOut.GetWidthType(); + SAL_INFO("vcl.fonts", oss.str()); +#endif + + if( bHaveSubstitute ) + { + rCachedFontMap.push_front(value_type(rFontSelData, aOut)); + // Fairly arbitrary limit in this case, but I recall measuring max 8 + // fonts as the typical max amount of fonts in medium sized documents, so make it + // a fair chunk larger to accommodate weird documents./ + if (rCachedFontMap.size() > 256) + rCachedFontMap.pop_back(); + rFontSelData = aOut; + } + + return bHaveSubstitute; +} + +bool FcGlyphFallbackSubstitution::FindFontSubstitute(vcl::font::FontSelectPattern& rFontSelData, + LogicalFontInstance* /*pLogicalFont*/, + OUString& rMissingCodes ) const +{ + // We don't actually want to talk to Fontconfig at all for symbol fonts + if( rFontSelData.IsSymbolFont() ) + return false; + // StarSymbol is a unicode font, but it still deserves the symbol flag + if ( IsStarSymbol(rFontSelData.maSearchName) ) + return false; + + const vcl::font::FontSelectPattern aOut = GetFcSubstitute( rFontSelData, rMissingCodes ); + // TODO: cache the unicode + srcfont specific result + // FC doing it would be preferable because it knows the invariables + // e.g. FC knows the FC rule that all Arial gets replaced by LiberationSans + // whereas we would have to check for every size or attribute + if( aOut.maSearchName.isEmpty() ) + return false; + + const bool bHaveSubstitute = !uselessmatch( rFontSelData, aOut ); + +#ifdef DEBUG + std::ostringstream oss; + oss << "FcGFSubstitution \"" + << rFontSelData.maTargetName + << "\" bipw=" + << rFontSelData.GetWeight() + << rFontSelData.GetItalic() + << rFontSelData.GetPitch() + << rFontSelData.GetWidthType() + << " -> "; + if( !bHaveSubstitute ) + oss << "no substitute available."; + else + oss << "\"" + << aOut.maSearchName + << "\" bipw=" + << aOut.GetWeight() + << aOut.GetItalic() + << aOut.GetPitch() + << aOut.GetWidthType(); + SAL_INFO("vcl.fonts", oss.str()); +#endif + + if( bHaveSubstitute ) + rFontSelData = aOut; + + return bHaveSubstitute; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/fontmanager/helper.cxx b/vcl/unx/generic/fontmanager/helper.cxx new file mode 100644 index 000000000..6cea0998e --- /dev/null +++ b/vcl/unx/generic/fontmanager/helper.cxx @@ -0,0 +1,262 @@ +/* -*- 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 <config_folders.h> + +#include <sys/stat.h> +#include <limits.h> +#include <osl/file.hxx> +#include <osl/process.h> +#include <osl/thread.h> +#include <rtl/bootstrap.hxx> +#include <rtl/ustring.hxx> +#include <sal/log.hxx> +#include <tools/urlobj.hxx> +#include <unx/helper.hxx> + +#include <tuple> + +using ::rtl::Bootstrap; + +namespace psp { + +OUString getOfficePath( whichOfficePath ePath ) +{ + static const auto aPaths = [] { + OUString sRoot, sUser, sConfig; + Bootstrap::get("BRAND_BASE_DIR", sRoot); + Bootstrap aBootstrap(sRoot + "/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("bootstrap")); + aBootstrap.getFrom("UserInstallation", sUser); + aBootstrap.getFrom("CustomDataUrl", sConfig); + OUString aUPath = sUser + "/user/psprint"; + if (sRoot.startsWith("file://")) + { + OUString aSysPath; + if (osl::FileBase::getSystemPathFromFileURL(sRoot, aSysPath) == osl::FileBase::E_None) + sRoot = aSysPath; + } + if (sUser.startsWith("file://")) + { + OUString aSysPath; + if (osl::FileBase::getSystemPathFromFileURL(sUser, aSysPath) == osl::FileBase::E_None) + sUser = aSysPath; + } + if (sConfig.startsWith("file://")) + { + OUString aSysPath; + if (osl::FileBase::getSystemPathFromFileURL(sConfig, aSysPath) == osl::FileBase::E_None) + sConfig = aSysPath; + } + + // ensure user path exists + SAL_INFO("vcl.fonts", "Trying to create: " << aUPath); + osl::Directory::createPath(aUPath); + + return std::make_tuple(sRoot, sUser, sConfig); + }(); + const auto& [aInstallationRootPath, aUserPath, aConfigPath] = aPaths; + + switch( ePath ) + { + case whichOfficePath::ConfigPath: return aConfigPath; + case whichOfficePath::InstallationRootPath: return aInstallationRootPath; + case whichOfficePath::UserPath: return aUserPath; + } + return OUString(); +} + +static OString getEnvironmentPath( const char* pKey ) +{ + OString aPath; + + const char* pValue = getenv( pKey ); + if( pValue && *pValue ) + { + aPath = OString( pValue ); + } + return aPath; +} + +} // namespace psp + +void psp::getPrinterPathList( std::vector< OUString >& rPathList, const char* pSubDir ) +{ + rPathList.clear(); + rtl_TextEncoding aEncoding = osl_getThreadTextEncoding(); + + OUStringBuffer aPathBuffer( 256 ); + + // append net path + aPathBuffer.append( getOfficePath( whichOfficePath::InstallationRootPath ) ); + if( !aPathBuffer.isEmpty() ) + { + aPathBuffer.append( "/" LIBO_SHARE_FOLDER "/psprint" ); + if( pSubDir ) + { + aPathBuffer.append( '/' ); + aPathBuffer.appendAscii( pSubDir ); + } + rPathList.push_back( aPathBuffer.makeStringAndClear() ); + } + // append user path + aPathBuffer.append( getOfficePath( whichOfficePath::UserPath ) ); + if( !aPathBuffer.isEmpty() ) + { + aPathBuffer.append( "/user/psprint" ); + if( pSubDir ) + { + aPathBuffer.append( '/' ); + aPathBuffer.appendAscii( pSubDir ); + } + rPathList.push_back( aPathBuffer.makeStringAndClear() ); + } + + OString aPath( getEnvironmentPath("SAL_PSPRINT") ); + sal_Int32 nIndex = 0; + do + { + OString aDir( aPath.getToken( 0, ':', nIndex ) ); + if( aDir.isEmpty() ) + continue; + + if( pSubDir ) + { + aDir += OString::Concat("/") + pSubDir; + } + struct stat aStat; + if( stat( aDir.getStr(), &aStat ) || ! S_ISDIR( aStat.st_mode ) ) + continue; + + rPathList.push_back( OStringToOUString( aDir, aEncoding ) ); + } while( nIndex != -1 ); + + #ifdef SYSTEM_PPD_DIR + if( pSubDir && rtl_str_compare( pSubDir, PRINTER_PPDDIR ) == 0 ) + { + rPathList.push_back( OStringToOUString( OString( SYSTEM_PPD_DIR ), RTL_TEXTENCODING_UTF8 ) ); + } + #endif + + if( !rPathList.empty() ) + return; + + // last resort: next to program file (mainly for setup) + OUString aExe; + if( osl_getExecutableFile( &aExe.pData ) == osl_Process_E_None ) + { + INetURLObject aDir( aExe ); + aDir.removeSegment(); + aExe = aDir.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + OUString aSysPath; + if( osl_getSystemPathFromFileURL( aExe.pData, &aSysPath.pData ) == osl_File_E_None ) + { + rPathList.push_back( aSysPath ); + } + } +} + +OUString const & psp::getFontPath() +{ + static OUString aPath; + + if (aPath.isEmpty()) + { + OUStringBuffer aPathBuffer( 512 ); + + OUString aConfigPath( getOfficePath( whichOfficePath::ConfigPath ) ); + OUString aInstallationRootPath( getOfficePath( whichOfficePath::InstallationRootPath ) ); + OUString aUserPath( getOfficePath( whichOfficePath::UserPath ) ); + if (!aInstallationRootPath.isEmpty()) + { + // internal font resources, required for normal operation, like OpenSymbol + aPathBuffer.append(aInstallationRootPath + + "/" LIBO_SHARE_RESOURCE_FOLDER "/common/fonts;"); + } + if( !aConfigPath.isEmpty() ) + { + // #i53530# Path from CustomDataUrl will completely + // replace net share and user paths if the path exists + OUString sPath = aConfigPath + "/" LIBO_SHARE_FOLDER "/fonts"; + // check existence of config path + struct stat aStat; + if( 0 != stat( OUStringToOString( sPath, osl_getThreadTextEncoding() ).getStr(), &aStat ) + || ! S_ISDIR( aStat.st_mode ) ) + aConfigPath.clear(); + else + { + aPathBuffer.append(sPath); + } + } + if( aConfigPath.isEmpty() ) + { + if( !aInstallationRootPath.isEmpty() ) + { + aPathBuffer.append( aInstallationRootPath ); + aPathBuffer.append( "/" LIBO_SHARE_FOLDER "/fonts/truetype;"); + } + if( !aUserPath.isEmpty() ) + { + aPathBuffer.append( aUserPath ); + aPathBuffer.append( "/user/fonts" ); + } + } + + aPath = aPathBuffer.makeStringAndClear(); + SAL_INFO("vcl.fonts", "Initializing font path to: " << aPath); + } + return aPath; +} + +void psp::normPath( OString& rPath ) +{ + char buf[PATH_MAX]; + + // double slashes and slash at end are probably + // removed by realpath anyway, but since this runs + // on many different platforms let's play it safe + OString aPath = rPath.replaceAll("//", "/"); + + if( aPath.endsWith("/") ) + aPath = aPath.copy(0, aPath.getLength()-1); + + if( ( aPath.indexOf("./") != -1 || + aPath.indexOf( '~' ) != -1 ) + && realpath( aPath.getStr(), buf ) ) + { + rPath = buf; + } + else + { + rPath = aPath; + } +} + +void psp::splitPath( OString& rPath, OString& rDir, OString& rBase ) +{ + normPath( rPath ); + sal_Int32 nIndex = rPath.lastIndexOf( '/' ); + if( nIndex > 0 ) + rDir = rPath.copy( 0, nIndex ); + else if( nIndex == 0 ) // root dir + rDir = rPath.copy( 0, 1 ); + if( rPath.getLength() > nIndex+1 ) + rBase = rPath.copy( nIndex+1 ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |