summaryrefslogtreecommitdiffstats
path: root/vcl/unx/generic/glyphs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--vcl/unx/generic/glyphs/freetype_glyphcache.cxx975
-rw-r--r--vcl/unx/generic/glyphs/glyphcache.cxx121
2 files changed, 1096 insertions, 0 deletions
diff --git a/vcl/unx/generic/glyphs/freetype_glyphcache.cxx b/vcl/unx/generic/glyphs/freetype_glyphcache.cxx
new file mode 100644
index 000000000..bb7d3e10e
--- /dev/null
+++ b/vcl/unx/generic/glyphs/freetype_glyphcache.cxx
@@ -0,0 +1,975 @@
+/* -*- 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 <o3tl/safeint.hxx>
+#include <vcl/fontcharmap.hxx>
+
+#include <unx/freetype_glyphcache.hxx>
+
+#include <fontinstance.hxx>
+#include <fontattributes.hxx>
+
+#include <unotools/fontdefs.hxx>
+
+#include <tools/poly.hxx>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+#include <basegfx/polygon/b2dpolypolygon.hxx>
+
+#include <sal/log.hxx>
+#include <osl/module.h>
+
+#include <langboost.hxx>
+#include <font/PhysicalFontCollection.hxx>
+#include <sft.hxx>
+
+#include <ft2build.h>
+#include FT_FREETYPE_H
+#include FT_GLYPH_H
+#include FT_MULTIPLE_MASTERS_H
+#include FT_OUTLINE_H
+#include FT_SIZES_H
+#include FT_SYNTHESIS_H
+#include FT_TRUETYPE_TABLES_H
+
+#include <vector>
+
+// TODO: move file mapping stuff to OSL
+#include <unistd.h>
+#include <dlfcn.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <unx/fontmanager.hxx>
+#include <impfontcharmap.hxx>
+
+static FT_Library aLibFT = nullptr;
+
+// TODO: remove when the priorities are selected by UI
+// if (AH==0) => disable autohinting
+// if (AA==0) => disable antialiasing
+// if (EB==0) => disable embedded bitmaps
+// if (AA prio <= AH prio) => antialias + autohint
+// if (AH<AA) => do not autohint when antialiasing
+// if (EB<AH) => do not autohint for monochrome
+static int nDefaultPrioEmbedded = 2;
+static int nDefaultPrioAntiAlias = 1;
+
+FreetypeFontFile::FreetypeFontFile( const OString& rNativeFileName )
+: maNativeFileName( rNativeFileName ),
+ mpFileMap( nullptr ),
+ mnFileSize( 0 ),
+ mnRefCount( 0 ),
+ mnLangBoost( 0 )
+{
+ // boost font preference if UI language is mentioned in filename
+ int nPos = maNativeFileName.lastIndexOf( '_' );
+ if( nPos == -1 || maNativeFileName[nPos+1] == '.' )
+ mnLangBoost += 0x1000; // no langinfo => good
+ else
+ {
+ static const char* pLangBoost = nullptr;
+ static bool bOnce = true;
+ if( bOnce )
+ {
+ bOnce = false;
+ pLangBoost = vcl::getLangBoost();
+ }
+
+ if( pLangBoost && !strncasecmp( pLangBoost, &maNativeFileName.getStr()[nPos+1], 3 ) )
+ mnLangBoost += 0x2000; // matching langinfo => better
+ }
+}
+
+bool FreetypeFontFile::Map()
+{
+ if (mnRefCount++ == 0)
+ {
+ const char* pFileName = maNativeFileName.getStr();
+ int nFile = open( pFileName, O_RDONLY );
+ if( nFile < 0 )
+ {
+ SAL_WARN("vcl.unx.freetype", "open('" << maNativeFileName << "') failed: " << strerror(errno));
+ return false;
+ }
+
+ struct stat aStat;
+ int nRet = fstat( nFile, &aStat );
+ if (nRet < 0)
+ {
+ SAL_WARN("vcl.unx.freetype", "fstat on '" << maNativeFileName << "' failed: " << strerror(errno));
+ close (nFile);
+ return false;
+ }
+ mnFileSize = aStat.st_size;
+ mpFileMap = static_cast<unsigned char*>(
+ mmap( nullptr, mnFileSize, PROT_READ, MAP_SHARED, nFile, 0 ));
+ if( mpFileMap == MAP_FAILED )
+ {
+ SAL_WARN("vcl.unx.freetype", "mmap of '" << maNativeFileName << "' failed: " << strerror(errno));
+ mpFileMap = nullptr;
+ }
+ close( nFile );
+ }
+
+ return (mpFileMap != nullptr);
+}
+
+void FreetypeFontFile::Unmap()
+{
+ if (--mnRefCount != 0)
+ return;
+ assert(mnRefCount >= 0 && "how did this go negative\n");
+ if (mpFileMap)
+ {
+ munmap(mpFileMap, mnFileSize);
+ mpFileMap = nullptr;
+ }
+}
+
+FreetypeFontInfo::FreetypeFontInfo( const FontAttributes& rDevFontAttributes,
+ FreetypeFontFile* const pFontFile, int nFaceNum, int nFaceVariation, sal_IntPtr nFontId)
+:
+ maFaceFT( nullptr ),
+ mpFontFile(pFontFile),
+ mnFaceNum( nFaceNum ),
+ mnFaceVariation( nFaceVariation ),
+ mnRefCount( 0 ),
+ mnFontId( nFontId ),
+ maDevFontAttributes( rDevFontAttributes )
+{
+ // prefer font with low ID
+ maDevFontAttributes.IncreaseQualityBy( 10000 - nFontId );
+ // prefer font with matching file names
+ maDevFontAttributes.IncreaseQualityBy( mpFontFile->GetLangBoost() );
+}
+
+FreetypeFontInfo::~FreetypeFontInfo()
+{
+}
+
+namespace
+{
+ void dlFT_Done_MM_Var(FT_Library library, FT_MM_Var *amaster)
+ {
+#if !HAVE_DLAPI
+ FT_Done_MM_Var(library, amaster);
+#else
+ static auto func = reinterpret_cast<void(*)(FT_Library, FT_MM_Var*)>(
+ osl_getAsciiFunctionSymbol(nullptr, "FT_Done_MM_Var"));
+ if (func)
+ func(library, amaster);
+ else
+ free(amaster);
+#endif
+ }
+}
+
+FT_FaceRec_* FreetypeFontInfo::GetFaceFT()
+{
+ if (!maFaceFT && mpFontFile->Map())
+ {
+ FT_Error rc = FT_New_Memory_Face( aLibFT,
+ mpFontFile->GetBuffer(),
+ mpFontFile->GetFileSize(), mnFaceNum, &maFaceFT );
+ if( (rc != FT_Err_Ok) || (maFaceFT->num_glyphs <= 0) )
+ maFaceFT = nullptr;
+
+ if (maFaceFT && mnFaceVariation)
+ {
+ FT_MM_Var *pFtMMVar;
+ if (FT_Get_MM_Var(maFaceFT, &pFtMMVar) == 0)
+ {
+ if (o3tl::make_unsigned(mnFaceVariation) <= pFtMMVar->num_namedstyles)
+ {
+ FT_Var_Named_Style *instance = &pFtMMVar->namedstyle[mnFaceVariation - 1];
+ FT_Set_Var_Design_Coordinates(maFaceFT, pFtMMVar->num_axis, instance->coords);
+ }
+ dlFT_Done_MM_Var(aLibFT, pFtMMVar);
+ }
+ }
+ }
+
+ ++mnRefCount;
+ return maFaceFT;
+}
+
+void FreetypeFont::SetFontVariationsOnHBFont(hb_font_t* pHbFace) const
+{
+ sal_uInt32 nFaceVariation = mxFontInfo->GetFontFaceVariation();
+ if (!(maFaceFT && nFaceVariation))
+ return;
+
+ FT_MM_Var *pFtMMVar;
+ if (FT_Get_MM_Var(maFaceFT, &pFtMMVar) != 0)
+ return;
+
+ if (nFaceVariation <= pFtMMVar->num_namedstyles)
+ {
+ FT_Var_Named_Style *instance = &pFtMMVar->namedstyle[nFaceVariation - 1];
+ std::vector<hb_variation_t> aVariations(pFtMMVar->num_axis);
+ for (FT_UInt i = 0; i < pFtMMVar->num_axis; ++i)
+ {
+ aVariations[i].tag = pFtMMVar->axis[i].tag;
+ aVariations[i].value = instance->coords[i] / 65536.0;
+ }
+ hb_font_set_variations(pHbFace, aVariations.data(), aVariations.size());
+ }
+ dlFT_Done_MM_Var(aLibFT, pFtMMVar);
+}
+
+void FreetypeFontInfo::ReleaseFaceFT()
+{
+ if (--mnRefCount == 0)
+ {
+ if (maFaceFT)
+ {
+ FT_Done_Face(maFaceFT);
+ maFaceFT = nullptr;
+ }
+ mpFontFile->Unmap();
+ }
+ assert(mnRefCount >= 0 && "how did this go negative\n");
+}
+
+static unsigned GetUInt( const unsigned char* p ) { return((p[0]<<24)+(p[1]<<16)+(p[2]<<8)+p[3]);}
+static unsigned GetUShort( const unsigned char* p ){ return((p[0]<<8)+p[1]);}
+
+const sal_uInt32 T_true = 0x74727565; /* 'true' */
+const sal_uInt32 T_ttcf = 0x74746366; /* 'ttcf' */
+const sal_uInt32 T_otto = 0x4f54544f; /* 'OTTO' */
+
+const unsigned char* FreetypeFontInfo::GetTable( const char* pTag, sal_uLong* pLength ) const
+{
+ const unsigned char* pBuffer = mpFontFile->GetBuffer();
+ int nFileSize = mpFontFile->GetFileSize();
+ if( !pBuffer || nFileSize<1024 )
+ return nullptr;
+
+ // we currently handle TTF, TTC and OTF headers
+ unsigned nFormat = GetUInt( pBuffer );
+
+ const unsigned char* p = pBuffer + 12;
+ if( nFormat == ::T_ttcf ) // TTC_MAGIC
+ p += GetUInt( p + 4 * mnFaceNum );
+ else if( nFormat != 0x00010000 && nFormat != ::T_true && nFormat != ::T_otto) // TTF_MAGIC and Apple TTF Magic and PS-OpenType font
+ return nullptr;
+
+ // walk table directory until match
+ int nTables = GetUShort( p - 8 );
+ if( nTables >= 64 ) // something fishy?
+ return nullptr;
+ for( int i = 0; i < nTables; ++i, p+=16 )
+ {
+ if( p[0]==pTag[0] && p[1]==pTag[1] && p[2]==pTag[2] && p[3]==pTag[3] )
+ {
+ sal_uLong nLength = GetUInt( p + 12 );
+ if( pLength != nullptr )
+ *pLength = nLength;
+ const unsigned char* pTable = pBuffer + GetUInt( p + 8 );
+ if( (pTable + nLength) <= (mpFontFile->GetBuffer() + nFileSize) )
+ return pTable;
+ }
+ }
+
+ return nullptr;
+}
+
+void FreetypeFontInfo::AnnounceFont( vcl::font::PhysicalFontCollection* pFontCollection )
+{
+ rtl::Reference<FreetypeFontFace> pFD(new FreetypeFontFace( this, maDevFontAttributes ));
+ pFontCollection->Add( pFD.get() );
+}
+
+void FreetypeManager::InitFreetype()
+{
+ /*FT_Error rcFT =*/ FT_Init_FreeType( &aLibFT );
+
+ // TODO: remove when the priorities are selected by UI
+ char* pEnv;
+ pEnv = ::getenv( "SAL_EMBEDDED_BITMAP_PRIORITY" );
+ if( pEnv )
+ nDefaultPrioEmbedded = pEnv[0] - '0';
+ pEnv = ::getenv( "SAL_ANTIALIASED_TEXT_PRIORITY" );
+ if( pEnv )
+ nDefaultPrioAntiAlias = pEnv[0] - '0';
+}
+
+namespace
+{
+ bool DoesAlmostHorizontalDrainRenderingPool()
+ {
+ FT_Int nMajor, nMinor, nPatch;
+ FT_Library_Version(aLibFT, &nMajor, &nMinor, &nPatch);
+ if (nMajor > 2)
+ return false;
+ if (nMajor == 2 && nMinor <= 8)
+ return true;
+ return false;
+ }
+}
+
+bool FreetypeFont::AlmostHorizontalDrainsRenderingPool(int nRatio, const vcl::font::FontSelectPattern& rFSD)
+{
+ static bool bAlmostHorizontalDrainsRenderingPool = DoesAlmostHorizontalDrainRenderingPool();
+ if (nRatio > 100 && rFSD.maTargetName == "OpenSymbol" && bAlmostHorizontalDrainsRenderingPool)
+ {
+ // tdf#127189 FreeType <= 2.8 will fail to render stretched horizontal
+ // brace glyphs in starmath at a fairly low stretch ratio. The failure
+ // will set CAIRO_STATUS_FREETYPE_ERROR on the surface which cannot be
+ // cleared, so all further painting to the surface fails.
+
+ // This appears fixed in >= freetype 2.9
+
+ // Restrict this bodge to a stretch ratio > ~10 of the OpenSymbol font
+ // where it has been seen in practice.
+ SAL_WARN("vcl", "rendering text would fail with stretch ratio of: " << nRatio << ", with FreeType <= 2.8");
+ return true;
+ }
+ return false;
+}
+
+FT_Face FreetypeFont::GetFtFace() const
+{
+ FT_Activate_Size( maSizeFT );
+
+ return maFaceFT;
+}
+
+void FreetypeManager::AddFontFile(const OString& rNormalizedName,
+ int nFaceNum, int nVariantNum, sal_IntPtr nFontId, const FontAttributes& rDevFontAttr)
+{
+ if( rNormalizedName.isEmpty() )
+ return;
+
+ if( m_aFontInfoList.find( nFontId ) != m_aFontInfoList.end() )
+ return;
+
+ FreetypeFontInfo* pFontInfo = new FreetypeFontInfo( rDevFontAttr,
+ FindFontFile(rNormalizedName), nFaceNum, nVariantNum, nFontId);
+ m_aFontInfoList[ nFontId ].reset(pFontInfo);
+ if( m_nMaxFontId < nFontId )
+ m_nMaxFontId = nFontId;
+}
+
+void FreetypeManager::AnnounceFonts( vcl::font::PhysicalFontCollection* pToAdd ) const
+{
+ for (auto const& font : m_aFontInfoList)
+ {
+ FreetypeFontInfo* pFreetypeFontInfo = font.second.get();
+ pFreetypeFontInfo->AnnounceFont( pToAdd );
+ }
+}
+
+FreetypeFont* FreetypeManager::CreateFont(FreetypeFontInstance* pFontInstance)
+{
+ // find a FontInfo matching to the font id
+ if (!pFontInstance)
+ return nullptr;
+
+ const vcl::font::PhysicalFontFace* pFontFace = pFontInstance->GetFontFace();
+ if (!pFontFace)
+ return nullptr;
+
+ sal_IntPtr nFontId = pFontFace->GetFontId();
+ FontInfoList::iterator it = m_aFontInfoList.find(nFontId);
+
+ if (it == m_aFontInfoList.end())
+ return nullptr;
+
+ return new FreetypeFont(*pFontInstance, it->second);
+}
+
+FreetypeFontFace::FreetypeFontFace( FreetypeFontInfo* pFI, const FontAttributes& rDFA )
+: vcl::font::PhysicalFontFace( rDFA ),
+ mpFreetypeFontInfo( pFI )
+{
+}
+
+rtl::Reference<LogicalFontInstance> FreetypeFontFace::CreateFontInstance(const vcl::font::FontSelectPattern& rFSD) const
+{
+ return new FreetypeFontInstance(*this, rFSD);
+}
+
+// FreetypeFont
+
+FreetypeFont::FreetypeFont(FreetypeFontInstance& rFontInstance, const std::shared_ptr<FreetypeFontInfo>& rFI)
+: mrFontInstance(rFontInstance),
+ mnCos( 0x10000),
+ mnSin( 0 ),
+ mnPrioAntiAlias(nDefaultPrioAntiAlias),
+ mxFontInfo(rFI),
+ mnLoadFlags( 0 ),
+ maFaceFT( nullptr ),
+ maSizeFT( nullptr ),
+ mbFaceOk( false ),
+ mbArtItalic( false ),
+ mbArtBold(false)
+{
+ int nPrioEmbedded = nDefaultPrioEmbedded;
+
+ maFaceFT = mxFontInfo->GetFaceFT();
+
+ const vcl::font::FontSelectPattern& rFSD = rFontInstance.GetFontSelectPattern();
+
+ if( rFSD.mnOrientation )
+ {
+ const double dRad = toRadians(rFSD.mnOrientation);
+ mnCos = static_cast<tools::Long>( 0x10000 * cos( dRad ) + 0.5 );
+ mnSin = static_cast<tools::Long>( 0x10000 * sin( dRad ) + 0.5 );
+ }
+
+ // set the pixel size of the font instance
+ mnWidth = rFSD.mnWidth;
+ if( !mnWidth )
+ mnWidth = rFSD.mnHeight;
+ if (rFSD.mnHeight == 0)
+ {
+ SAL_WARN("vcl", "FreetypeFont divide by zero");
+ mfStretch = 1.0;
+ return;
+ }
+ mfStretch = static_cast<double>(mnWidth) / rFSD.mnHeight;
+ // sanity checks (e.g. #i66394#, #i66244#, #i66537#)
+ if (mnWidth < 0)
+ {
+ SAL_WARN("vcl", "FreetypeFont negative font width of: " << mnWidth);
+ return;
+ }
+ if (mfStretch > +148.0 || mfStretch < -148.0)
+ {
+ SAL_WARN("vcl", "FreetypeFont excessive stretch of: " << mfStretch);
+ return;
+ }
+
+ if( !maFaceFT )
+ return;
+
+ FT_New_Size( maFaceFT, &maSizeFT );
+ FT_Activate_Size( maSizeFT );
+ /* This might fail for color bitmap fonts, but that is fine since we will
+ * not need any glyph data from FreeType in this case */
+ /*FT_Error rc = */ FT_Set_Pixel_Sizes( maFaceFT, mnWidth, rFSD.mnHeight );
+
+ FT_Select_Charmap(maFaceFT, FT_ENCODING_UNICODE);
+
+ if( mxFontInfo->IsSymbolFont() )
+ {
+ FT_Encoding eEncoding = FT_ENCODING_MS_SYMBOL;
+ FT_Select_Charmap(maFaceFT, eEncoding);
+ }
+
+ mbFaceOk = true;
+
+ // TODO: query GASP table for load flags
+ mnLoadFlags = FT_LOAD_DEFAULT | FT_LOAD_IGNORE_TRANSFORM;
+
+ mbArtItalic = (rFSD.GetItalic() != ITALIC_NONE && mxFontInfo->GetFontAttributes().GetItalic() == ITALIC_NONE);
+ mbArtBold = (rFSD.GetWeight() > WEIGHT_MEDIUM && mxFontInfo->GetFontAttributes().GetWeight() <= WEIGHT_MEDIUM);
+
+ if( ((mnCos != 0) && (mnSin != 0)) || (nPrioEmbedded <= 0) )
+ mnLoadFlags |= FT_LOAD_NO_BITMAP;
+}
+
+namespace
+{
+ std::unique_ptr<FontConfigFontOptions> GetFCFontOptions(const FontAttributes& rFontAttributes, int nSize)
+ {
+ return psp::PrintFontManager::getFontOptions(rFontAttributes, nSize);
+ }
+}
+
+const FontConfigFontOptions* FreetypeFont::GetFontOptions() const
+{
+ if (!mxFontOptions)
+ {
+ mxFontOptions = GetFCFontOptions(mxFontInfo->GetFontAttributes(), mrFontInstance.GetFontSelectPattern().mnHeight);
+ mxFontOptions->SyncPattern(GetFontFileName(), GetFontFaceIndex(), GetFontFaceVariation(), NeedsArtificialBold());
+ }
+ return mxFontOptions.get();
+}
+
+const OString& FreetypeFont::GetFontFileName() const
+{
+ return mxFontInfo->GetFontFileName();
+}
+
+int FreetypeFont::GetFontFaceIndex() const
+{
+ return mxFontInfo->GetFontFaceIndex();
+}
+
+int FreetypeFont::GetFontFaceVariation() const
+{
+ return mxFontInfo->GetFontFaceVariation();
+}
+
+FreetypeFont::~FreetypeFont()
+{
+ if( maSizeFT )
+ FT_Done_Size( maSizeFT );
+
+ mxFontInfo->ReleaseFaceFT();
+}
+
+void FreetypeFont::GetFontMetric(ImplFontMetricDataRef const & rxTo) const
+{
+ rxTo->FontAttributes::operator =(mxFontInfo->GetFontAttributes());
+
+ rxTo->SetOrientation(mrFontInstance.GetFontSelectPattern().mnOrientation);
+
+ //Always consider [star]symbol as symbol fonts
+ if ( IsStarSymbol( rxTo->GetFamilyName() ) )
+ rxTo->SetSymbolFlag( true );
+
+ FT_Activate_Size( maSizeFT );
+
+ rxTo->ImplCalcLineSpacing(&mrFontInstance);
+ rxTo->ImplInitBaselines(&mrFontInstance);
+
+ rxTo->SetSlant( 0 );
+ rxTo->SetWidth( mnWidth );
+
+ const TT_OS2* pOS2 = static_cast<const TT_OS2*>(FT_Get_Sfnt_Table( maFaceFT, ft_sfnt_os2 ));
+ if( pOS2 && (pOS2->version != 0xFFFF) )
+ {
+ // map the panose info from the OS2 table to their VCL counterparts
+ switch( pOS2->panose[0] )
+ {
+ case 1: rxTo->SetFamilyType( FAMILY_ROMAN ); break;
+ case 2: rxTo->SetFamilyType( FAMILY_SWISS ); break;
+ case 3: rxTo->SetFamilyType( FAMILY_MODERN ); break;
+ case 4: rxTo->SetFamilyType( FAMILY_SCRIPT ); break;
+ case 5: rxTo->SetFamilyType( FAMILY_DECORATIVE ); break;
+ // TODO: is it reasonable to override the attribute with DONTKNOW?
+ case 0: // fall through
+ default: rxTo->SetFamilyType( FAMILY_DONTKNOW ); break;
+ }
+
+ switch( pOS2->panose[3] )
+ {
+ case 2: // fall through
+ case 3: // fall through
+ case 4: // fall through
+ case 5: // fall through
+ case 6: // fall through
+ case 7: // fall through
+ case 8: rxTo->SetPitch( PITCH_VARIABLE ); break;
+ case 9: rxTo->SetPitch( PITCH_FIXED ); break;
+ // TODO: is it reasonable to override the attribute with DONTKNOW?
+ case 0: // fall through
+ case 1: // fall through
+ default: rxTo->SetPitch( PITCH_DONTKNOW ); break;
+ }
+ }
+
+ // initialize kashida width
+ rxTo->SetMinKashida(mrFontInstance.GetKashidaWidth());
+}
+
+void FreetypeFont::ApplyGlyphTransform(bool bVertical, FT_Glyph pGlyphFT ) const
+{
+ // shortcut most common case
+ if (!mrFontInstance.GetFontSelectPattern().mnOrientation && !bVertical)
+ return;
+
+ const FT_Size_Metrics& rMetrics = maFaceFT->size->metrics;
+ FT_Vector aVector;
+ FT_Matrix aMatrix;
+
+ bool bStretched = false;
+
+ if (!bVertical)
+ {
+ // straight
+ aVector.x = 0;
+ aVector.y = 0;
+ aMatrix.xx = +mnCos;
+ aMatrix.yy = +mnCos;
+ aMatrix.xy = -mnSin;
+ aMatrix.yx = +mnSin;
+ }
+ else
+ {
+ // left
+ bStretched = (mfStretch != 1.0);
+ aVector.x = static_cast<FT_Pos>(+rMetrics.descender * mfStretch);
+ aVector.y = -rMetrics.ascender;
+ aMatrix.xx = static_cast<FT_Pos>(-mnSin / mfStretch);
+ aMatrix.yy = static_cast<FT_Pos>(-mnSin * mfStretch);
+ aMatrix.xy = static_cast<FT_Pos>(-mnCos * mfStretch);
+ aMatrix.yx = static_cast<FT_Pos>(+mnCos / mfStretch);
+ }
+
+ if( pGlyphFT->format != FT_GLYPH_FORMAT_BITMAP )
+ {
+ FT_Glyph_Transform( pGlyphFT, nullptr, &aVector );
+
+ // orthogonal transforms are better handled by bitmap operations
+ if( bStretched )
+ {
+ // apply non-orthogonal or stretch transformations
+ FT_Glyph_Transform( pGlyphFT, &aMatrix, nullptr );
+ }
+ }
+ else
+ {
+ // FT<=2005 ignores transforms for bitmaps, so do it manually
+ FT_BitmapGlyph pBmpGlyphFT = reinterpret_cast<FT_BitmapGlyph>(pGlyphFT);
+ pBmpGlyphFT->left += (aVector.x + 32) >> 6;
+ pBmpGlyphFT->top += (aVector.y + 32) >> 6;
+ }
+}
+
+bool FreetypeFont::GetGlyphBoundRect(sal_GlyphId nID, tools::Rectangle& rRect, bool bVertical) const
+{
+ FT_Activate_Size( maSizeFT );
+
+ FT_Error rc = FT_Load_Glyph(maFaceFT, nID, mnLoadFlags);
+
+ if (rc != FT_Err_Ok)
+ return false;
+
+ if (mbArtBold)
+ FT_GlyphSlot_Embolden(maFaceFT->glyph);
+
+ FT_Glyph pGlyphFT;
+ rc = FT_Get_Glyph(maFaceFT->glyph, &pGlyphFT);
+ if (rc != FT_Err_Ok)
+ return false;
+
+ ApplyGlyphTransform(bVertical, pGlyphFT);
+
+ FT_BBox aBbox;
+ FT_Glyph_Get_CBox( pGlyphFT, FT_GLYPH_BBOX_PIXELS, &aBbox );
+ FT_Done_Glyph( pGlyphFT );
+
+ tools::Rectangle aRect(aBbox.xMin, -aBbox.yMax, aBbox.xMax, -aBbox.yMin);
+ if (mnCos != 0x10000 && mnSin != 0)
+ {
+ const double nCos = mnCos / 65536.0;
+ const double nSin = mnSin / 65536.0;
+ rRect.SetLeft( nCos*aRect.Left() + nSin*aRect.Top() );
+ rRect.SetTop( -nSin*aRect.Left() - nCos*aRect.Top() );
+ rRect.SetRight( nCos*aRect.Right() + nSin*aRect.Bottom() );
+ rRect.SetBottom( -nSin*aRect.Right() - nCos*aRect.Bottom() );
+ }
+ else
+ rRect = aRect;
+ return true;
+}
+
+bool FreetypeFont::GetAntialiasAdvice() const
+{
+ // TODO: also use GASP info
+ return !mrFontInstance.GetFontSelectPattern().mbNonAntialiased && (mnPrioAntiAlias > 0);
+}
+
+// determine unicode ranges in font
+
+const FontCharMapRef & FreetypeFont::GetFontCharMap() const
+{
+ return mxFontInfo->GetFontCharMap();
+}
+
+bool FreetypeFont::GetFontCapabilities(vcl::FontCapabilities &rFontCapabilities) const
+{
+ return mxFontInfo->GetFontCapabilities(rFontCapabilities);
+}
+
+const FontCharMapRef & FreetypeFontInfo::GetFontCharMap() const
+{
+ // check if the charmap is already cached
+ if( mxFontCharMap.is() )
+ return mxFontCharMap;
+
+ // get the charmap and cache it
+ CmapResult aCmapResult;
+ aCmapResult.mbSymbolic = IsSymbolFont();
+
+ sal_uLong nLength = 0;
+ const unsigned char* pCmap = GetTable("cmap", &nLength);
+ if (pCmap && (nLength > 0) && ParseCMAP(pCmap, nLength, aCmapResult))
+ {
+ FontCharMapRef xFontCharMap( new FontCharMap ( aCmapResult ) );
+ mxFontCharMap = xFontCharMap;
+ }
+ else
+ {
+ FontCharMapRef xFontCharMap( new FontCharMap() );
+ mxFontCharMap = xFontCharMap;
+ }
+ // mxFontCharMap on either branch now has a refcount of 1
+ return mxFontCharMap;
+}
+
+bool FreetypeFontInfo::GetFontCapabilities(vcl::FontCapabilities &rFontCapabilities) const
+{
+ bool bRet = false;
+
+ sal_uLong nLength = 0;
+
+ // load OS/2 table
+ const FT_Byte* pOS2 = GetTable("OS/2", &nLength);
+ if (pOS2)
+ {
+ bRet = vcl::getTTCoverage(
+ rFontCapabilities.oUnicodeRange,
+ rFontCapabilities.oCodePageRange,
+ pOS2, nLength);
+ }
+
+ return bRet;
+}
+
+// outline stuff
+
+namespace {
+
+class PolyArgs
+{
+public:
+ PolyArgs( tools::PolyPolygon& rPolyPoly, sal_uInt16 nMaxPoints );
+
+ void AddPoint( tools::Long nX, tools::Long nY, PolyFlags);
+ void ClosePolygon();
+
+ tools::Long GetPosX() const { return maPosition.x;}
+ tools::Long GetPosY() const { return maPosition.y;}
+
+private:
+ tools::PolyPolygon& mrPolyPoly;
+
+ std::unique_ptr<Point[]>
+ mpPointAry;
+ std::unique_ptr<PolyFlags[]>
+ mpFlagAry;
+
+ FT_Vector maPosition;
+ sal_uInt16 mnMaxPoints;
+ sal_uInt16 mnPoints;
+ sal_uInt16 mnPoly;
+ bool bHasOffline;
+
+ PolyArgs(const PolyArgs&) = delete;
+ PolyArgs& operator=(const PolyArgs&) = delete;
+};
+
+}
+
+PolyArgs::PolyArgs( tools::PolyPolygon& rPolyPoly, sal_uInt16 nMaxPoints )
+: mrPolyPoly(rPolyPoly),
+ mnMaxPoints(nMaxPoints),
+ mnPoints(0),
+ mnPoly(0),
+ bHasOffline(false)
+{
+ mpPointAry.reset( new Point[ mnMaxPoints ] );
+ mpFlagAry.reset( new PolyFlags [ mnMaxPoints ] );
+ maPosition.x = maPosition.y = 0;
+}
+
+void PolyArgs::AddPoint( tools::Long nX, tools::Long nY, PolyFlags aFlag )
+{
+ SAL_WARN_IF( (mnPoints >= mnMaxPoints), "vcl", "FTGlyphOutline: AddPoint overflow!" );
+ if( mnPoints >= mnMaxPoints )
+ return;
+
+ maPosition.x = nX;
+ maPosition.y = nY;
+ mpPointAry[ mnPoints ] = Point( nX, nY );
+ mpFlagAry[ mnPoints++ ]= aFlag;
+ bHasOffline |= (aFlag != PolyFlags::Normal);
+}
+
+void PolyArgs::ClosePolygon()
+{
+ if( !mnPoly++ )
+ return;
+
+ // freetype seems to always close the polygon with an ON_CURVE point
+ // PolyPoly wants to close the polygon itself => remove last point
+ SAL_WARN_IF( (mnPoints < 2), "vcl", "FTGlyphOutline: PolyFinishNum failed!" );
+ --mnPoints;
+ SAL_WARN_IF( (mpPointAry[0]!=mpPointAry[mnPoints]), "vcl", "FTGlyphOutline: PolyFinishEq failed!" );
+ SAL_WARN_IF( (mpFlagAry[0]!=PolyFlags::Normal), "vcl", "FTGlyphOutline: PolyFinishFE failed!" );
+ SAL_WARN_IF( (mpFlagAry[mnPoints]!=PolyFlags::Normal), "vcl", "FTGlyphOutline: PolyFinishFS failed!" );
+
+ tools::Polygon aPoly( mnPoints, mpPointAry.get(), (bHasOffline ? mpFlagAry.get() : nullptr) );
+
+ // #i35928#
+ // This may be an invalid polygon, e.g. the last point is a control point.
+ // So close the polygon (and add the first point again) if the last point
+ // is a control point or different from first.
+ // #i48298#
+ // Now really duplicating the first point, to close or correct the
+ // polygon. Also no longer duplicating the flags, but enforcing
+ // PolyFlags::Normal for the newly added last point.
+ const sal_uInt16 nPolySize(aPoly.GetSize());
+ if(nPolySize)
+ {
+ if((aPoly.HasFlags() && PolyFlags::Control == aPoly.GetFlags(nPolySize - 1))
+ || (aPoly.GetPoint(nPolySize - 1) != aPoly.GetPoint(0)))
+ {
+ aPoly.SetSize(nPolySize + 1);
+ aPoly.SetPoint(aPoly.GetPoint(0), nPolySize);
+
+ if(aPoly.HasFlags())
+ {
+ aPoly.SetFlags(nPolySize, PolyFlags::Normal);
+ }
+ }
+ }
+
+ mrPolyPoly.Insert( aPoly );
+ mnPoints = 0;
+ bHasOffline = false;
+}
+
+extern "C" {
+
+// TODO: wait till all compilers accept that calling conventions
+// for functions are the same independent of implementation constness,
+// then uncomment the const-tokens in the function interfaces below
+static int FT_move_to( const FT_Vector* p0, void* vpPolyArgs )
+{
+ PolyArgs& rA = *static_cast<PolyArgs*>(vpPolyArgs);
+
+ // move_to implies a new polygon => finish old polygon first
+ rA.ClosePolygon();
+
+ rA.AddPoint( p0->x, p0->y, PolyFlags::Normal );
+ return 0;
+}
+
+static int FT_line_to( const FT_Vector* p1, void* vpPolyArgs )
+{
+ PolyArgs& rA = *static_cast<PolyArgs*>(vpPolyArgs);
+ rA.AddPoint( p1->x, p1->y, PolyFlags::Normal );
+ return 0;
+}
+
+static int FT_conic_to( const FT_Vector* p1, const FT_Vector* p2, void* vpPolyArgs )
+{
+ PolyArgs& rA = *static_cast<PolyArgs*>(vpPolyArgs);
+
+ // VCL's Polygon only knows cubic beziers
+ const tools::Long nX1 = (2 * rA.GetPosX() + 4 * p1->x + 3) / 6;
+ const tools::Long nY1 = (2 * rA.GetPosY() + 4 * p1->y + 3) / 6;
+ rA.AddPoint( nX1, nY1, PolyFlags::Control );
+
+ const tools::Long nX2 = (2 * p2->x + 4 * p1->x + 3) / 6;
+ const tools::Long nY2 = (2 * p2->y + 4 * p1->y + 3) / 6;
+ rA.AddPoint( nX2, nY2, PolyFlags::Control );
+
+ rA.AddPoint( p2->x, p2->y, PolyFlags::Normal );
+ return 0;
+}
+
+static int FT_cubic_to( const FT_Vector* p1, const FT_Vector* p2, const FT_Vector* p3, void* vpPolyArgs )
+{
+ PolyArgs& rA = *static_cast<PolyArgs*>(vpPolyArgs);
+ rA.AddPoint( p1->x, p1->y, PolyFlags::Control );
+ rA.AddPoint( p2->x, p2->y, PolyFlags::Control );
+ rA.AddPoint( p3->x, p3->y, PolyFlags::Normal );
+ return 0;
+}
+
+} // extern "C"
+
+bool FreetypeFont::GetGlyphOutline(sal_GlyphId nId, basegfx::B2DPolyPolygon& rB2DPolyPoly, bool bIsVertical) const
+{
+ if( maSizeFT )
+ FT_Activate_Size( maSizeFT );
+
+ rB2DPolyPoly.clear();
+
+ FT_Int nLoadFlags = FT_LOAD_DEFAULT | FT_LOAD_IGNORE_TRANSFORM;
+
+#ifdef FT_LOAD_TARGET_LIGHT
+ // enable "light hinting" if available
+ nLoadFlags |= FT_LOAD_TARGET_LIGHT;
+#endif
+
+ FT_Error rc = FT_Load_Glyph(maFaceFT, nId, nLoadFlags);
+ if( rc != FT_Err_Ok )
+ return false;
+
+ if (mbArtBold)
+ FT_GlyphSlot_Embolden(maFaceFT->glyph);
+
+ FT_Glyph pGlyphFT;
+ rc = FT_Get_Glyph( maFaceFT->glyph, &pGlyphFT );
+ if( rc != FT_Err_Ok )
+ return false;
+
+ if( pGlyphFT->format != FT_GLYPH_FORMAT_OUTLINE )
+ {
+ FT_Done_Glyph( pGlyphFT );
+ return false;
+ }
+
+ if( mbArtItalic )
+ {
+ FT_Matrix aMatrix;
+ aMatrix.xx = aMatrix.yy = 0x10000L;
+ aMatrix.xy = 0x6000L;
+ aMatrix.yx = 0;
+ FT_Glyph_Transform( pGlyphFT, &aMatrix, nullptr );
+ }
+
+ FT_Outline& rOutline = reinterpret_cast<FT_OutlineGlyphRec*>(pGlyphFT)->outline;
+ if( !rOutline.n_points ) // blank glyphs are ok
+ {
+ FT_Done_Glyph( pGlyphFT );
+ return true;
+ }
+
+ tools::Long nMaxPoints = 1 + rOutline.n_points * 3;
+ tools::PolyPolygon aToolPolyPolygon;
+ PolyArgs aPolyArg( aToolPolyPolygon, nMaxPoints );
+
+ ApplyGlyphTransform(bIsVertical, pGlyphFT);
+
+ FT_Outline_Funcs aFuncs;
+ aFuncs.move_to = &FT_move_to;
+ aFuncs.line_to = &FT_line_to;
+ aFuncs.conic_to = &FT_conic_to;
+ aFuncs.cubic_to = &FT_cubic_to;
+ aFuncs.shift = 0;
+ aFuncs.delta = 0;
+ FT_Outline_Decompose( &rOutline, &aFuncs, static_cast<void*>(&aPolyArg) );
+ aPolyArg.ClosePolygon(); // close last polygon
+ FT_Done_Glyph( pGlyphFT );
+
+ // convert to basegfx polypolygon
+ // TODO: get rid of the intermediate tools polypolygon
+ rB2DPolyPoly = aToolPolyPolygon.getB2DPolyPolygon();
+ rB2DPolyPoly.transform(basegfx::utils::createScaleB2DHomMatrix( +0x1p-6, -0x1p-6 ));
+
+ return true;
+}
+
+const unsigned char* FreetypeFont::GetTable(const char* pName, sal_uLong* pLength) const
+{
+ return mxFontInfo->GetTable( pName, pLength );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/unx/generic/glyphs/glyphcache.cxx b/vcl/unx/generic/glyphs/glyphcache.cxx
new file mode 100644
index 000000000..79db2d87b
--- /dev/null
+++ b/vcl/unx/generic/glyphs/glyphcache.cxx
@@ -0,0 +1,121 @@
+/* -*- 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 <stdlib.h>
+#include <unx/freetype_glyphcache.hxx>
+#include <unx/gendata.hxx>
+
+#include <fontinstance.hxx>
+
+#include <rtl/ustring.hxx>
+#include <sal/log.hxx>
+
+FreetypeManager::FreetypeManager()
+ : m_nMaxFontId(0)
+{
+ InitFreetype();
+}
+
+FreetypeManager::~FreetypeManager()
+{
+ ClearFontCache();
+}
+
+void FreetypeManager::ClearFontCache()
+{
+ m_aFontInfoList.clear();
+}
+
+FreetypeManager& FreetypeManager::get()
+{
+ GenericUnixSalData* const pSalData(GetGenericUnixSalData());
+ assert(pSalData);
+ return *pSalData->GetFreetypeManager();
+}
+
+FreetypeFontFile* FreetypeManager::FindFontFile(const OString& rNativeFileName)
+{
+ // font file already known? (e.g. for ttc, synthetic, aliased fonts)
+ const char* pFileName = rNativeFileName.getStr();
+ FontFileList::const_iterator it = m_aFontFileList.find(pFileName);
+ if (it != m_aFontFileList.end())
+ return it->second.get();
+
+ // no => create new one
+ FreetypeFontFile* pFontFile = new FreetypeFontFile(rNativeFileName);
+ pFileName = pFontFile->maNativeFileName.getStr();
+ m_aFontFileList[pFileName].reset(pFontFile);
+ return pFontFile;
+}
+
+FreetypeFontInstance::FreetypeFontInstance(const vcl::font::PhysicalFontFace& rPFF, const vcl::font::FontSelectPattern& rFSP)
+ : LogicalFontInstance(rPFF, rFSP)
+ , mxFreetypeFont(FreetypeManager::get().CreateFont(this))
+{
+}
+
+FreetypeFontInstance::~FreetypeFontInstance()
+{
+}
+
+static hb_blob_t* getFontTable(hb_face_t* /*face*/, hb_tag_t nTableTag, void* pUserData)
+{
+ char pTagName[5];
+ LogicalFontInstance::DecodeOpenTypeTag( nTableTag, pTagName );
+
+ sal_uLong nLength = 0;
+ FreetypeFontInstance* pFontInstance = static_cast<FreetypeFontInstance*>( pUserData );
+ FreetypeFont& rFont = pFontInstance->GetFreetypeFont();
+ const char* pBuffer = reinterpret_cast<const char*>(
+ rFont.GetTable(pTagName, &nLength) );
+
+ hb_blob_t* pBlob = nullptr;
+ if (pBuffer != nullptr)
+ pBlob = hb_blob_create(pBuffer, nLength, HB_MEMORY_MODE_READONLY, nullptr, nullptr);
+
+ return pBlob;
+}
+
+hb_font_t* FreetypeFontInstance::ImplInitHbFont()
+{
+ hb_font_t* pRet = InitHbFont(hb_face_create_for_tables(getFontTable, this, nullptr));
+ assert(mxFreetypeFont);
+ mxFreetypeFont->SetFontVariationsOnHBFont(pRet);
+ return pRet;
+}
+
+bool FreetypeFontInstance::ImplGetGlyphBoundRect(sal_GlyphId nId, tools::Rectangle& rRect, bool bVertical) const
+{
+ assert(mxFreetypeFont);
+ if (!mxFreetypeFont)
+ return false;
+ return mxFreetypeFont->GetGlyphBoundRect(nId, rRect, bVertical);
+}
+
+bool FreetypeFontInstance::GetGlyphOutline(sal_GlyphId nId, basegfx::B2DPolyPolygon& rPoly, bool bVertical) const
+{
+ assert(mxFreetypeFont);
+ if (!mxFreetypeFont)
+ return false;
+ return mxFreetypeFont->GetGlyphOutline(nId, rPoly, bVertical);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */