summaryrefslogtreecommitdiffstats
path: root/vcl/unx/generic/glyphs
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 05:54:39 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 05:54:39 +0000
commit267c6f2ac71f92999e969232431ba04678e7437e (patch)
tree358c9467650e1d0a1d7227a21dac2e3d08b622b2 /vcl/unx/generic/glyphs
parentInitial commit. (diff)
downloadlibreoffice-267c6f2ac71f92999e969232431ba04678e7437e.tar.xz
libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.zip
Adding upstream version 4:24.2.0.upstream/4%24.2.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vcl/unx/generic/glyphs')
-rw-r--r--vcl/unx/generic/glyphs/freetype_glyphcache.cxx824
-rw-r--r--vcl/unx/generic/glyphs/glyphcache.cxx86
2 files changed, 910 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 0000000000..5745f94a22
--- /dev/null
+++ b/vcl/unx/generic/glyphs/freetype_glyphcache.cxx
@@ -0,0 +1,824 @@
+/* -*- 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 <utility>
+#include <vcl/fontcharmap.hxx>
+
+#include <unx/freetype_glyphcache.hxx>
+
+#include <font/LogicalFontInstance.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 <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( OString aNativeFileName )
+: maNativeFileName(std::move( aNativeFileName )),
+ 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;
+ int nFD;
+ int n;
+ if( sscanf( pFileName, "/:FD:/%d%n", &nFD, &n ) == 1 && pFileName[n] == '\0' )
+ {
+ lseek( nFD, 0, SEEK_SET );
+ nFile = dup( nFD );
+ }
+ else
+ 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;
+ }
+ else
+ SAL_INFO("vcl.unx.freetype", "mmap'ed '" << maNativeFileName << "' successfully");
+ 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( FontAttributes aDevFontAttributes,
+ FreetypeFontFile* const pFontFile, int nFaceNum, int nFaceVariation, sal_IntPtr nFontId)
+:
+ maFaceFT( nullptr ),
+ mpFontFile(pFontFile),
+ mnFaceNum( nFaceNum ),
+ mnFaceVariation( nFaceVariation ),
+ mnRefCount( 0 ),
+ mnFontId( nFontId ),
+ maDevFontAttributes(std::move( aDevFontAttributes ))
+{
+ // 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 FreetypeFontInfo::ReleaseFaceFT()
+{
+ if (--mnRefCount == 0)
+ {
+ if (maFaceFT)
+ {
+ FT_Done_Face(maFaceFT);
+ maFaceFT = nullptr;
+ }
+ mpFontFile->Unmap();
+ }
+ assert(mnRefCount >= 0 && "how did this go negative\n");
+}
+
+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';
+}
+
+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);
+}
+
+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 )
+{
+ assert(mpFreetypeFontInfo);
+}
+
+rtl::Reference<LogicalFontInstance> FreetypeFontFace::CreateFontInstance(const vcl::font::FontSelectPattern& rFSD) const
+{
+ return new FreetypeFontInstance(*this, rFSD);
+}
+
+namespace
+{
+hb_blob_t* CreateHbBlob(FreetypeFontFile* pFontFile)
+{
+ auto pFileName = pFontFile->GetFileName().getStr();
+ int nFD;
+ int n;
+ if (sscanf(pFileName, "/:FD:/%d%n", &nFD, &n) == 1 && pFileName[n] == '\0')
+ {
+ if (pFontFile->Map())
+ return hb_blob_create(
+ reinterpret_cast<const char*>(pFontFile->GetBuffer()), pFontFile->GetFileSize(),
+ HB_MEMORY_MODE_READONLY, pFontFile,
+ [](void* data) { static_cast<FreetypeFontFile*>(data)->Unmap(); });
+ pFontFile->Unmap();
+ return hb_blob_get_empty();
+ }
+ return hb_blob_create_from_file(pFileName);
+}
+}
+
+hb_face_t* FreetypeFontFace::GetHbFace() const
+{
+ if (!mpHbFace)
+ {
+ auto pFontFile = mpFreetypeFontInfo->GetFontFile();
+ auto nIndex = mpFreetypeFontInfo->GetFontFaceIndex();
+ auto pHbBlob = CreateHbBlob(pFontFile);
+ mpHbFace = hb_face_create(pHbBlob, nIndex);
+ hb_blob_destroy(pHbBlob);
+ }
+ return mpHbFace;
+}
+
+hb_blob_t* FreetypeFontFace::GetHbTable(hb_tag_t nTag) const
+{
+ return hb_face_reference_table(mpHbFace, nTag);
+}
+
+const std::vector<hb_variation_t>& FreetypeFontFace::GetVariations(const LogicalFontInstance&) const
+{
+ if (!mxVariations)
+ {
+ mxVariations.emplace();
+ FT_Face aFaceFT = mpFreetypeFontInfo->GetFaceFT();
+ sal_uInt32 nFaceVariation = mpFreetypeFontInfo->GetFontFaceVariation();
+ if (!(aFaceFT && nFaceVariation))
+ return *mxVariations;
+
+ FT_MM_Var* pFtMMVar;
+ if (FT_Get_MM_Var(aFaceFT, &pFtMMVar) != 0)
+ return *mxVariations;
+
+ if (nFaceVariation <= pFtMMVar->num_namedstyles)
+ {
+ FT_Var_Named_Style* instance = &pFtMMVar->namedstyle[nFaceVariation - 1];
+ mxVariations->resize(pFtMMVar->num_axis);
+ for (FT_UInt i = 0; i < pFtMMVar->num_axis; ++i)
+ {
+ (*mxVariations)[i].tag = pFtMMVar->axis[i].tag;
+ (*mxVariations)[i].value = instance->coords[i] / 65536.0;
+ }
+ }
+ dlFT_Done_MM_Var(aLibFT, pFtMMVar);
+ }
+
+ return *mxVariations;
+}
+
+// FreetypeFont
+
+FreetypeFont::FreetypeFont(FreetypeFontInstance& rFontInstance, std::shared_ptr<FreetypeFontInfo> xFI)
+: mrFontInstance(rFontInstance),
+ mnCos( 0x10000),
+ mnSin( 0 ),
+ mnPrioAntiAlias(nDefaultPrioAntiAlias),
+ mxFontInfo(std::move(xFI)),
+ maFaceFT( nullptr ),
+ maSizeFT( nullptr ),
+ mbFaceOk( false )
+{
+ 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;
+ }
+
+ SAL_WARN_IF(mfStretch > +64.0 || mfStretch < -64.0, "vcl", "FreetypeFont excessive stretch of: " << mfStretch);
+
+ 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 );
+
+ mbFaceOk = true;
+}
+
+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(), mrFontInstance.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(FontMetricDataRef const & rxTo) const
+{
+ rxTo->FontAttributes::operator =(mxFontInfo->GetFontAttributes());
+
+ rxTo->SetOrientation(mrFontInstance.GetFontSelectPattern().mnOrientation);
+
+ 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::GetAntialiasAdvice() const
+{
+ // TODO: also use GASP info
+ return !mrFontInstance.GetFontSelectPattern().mbNonAntialiased && (mnPrioAntiAlias > 0);
+}
+
+// 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 (mrFontInstance.NeedsArtificialBold())
+ 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 (mrFontInstance.NeedsArtificialItalic())
+ {
+ FT_Matrix aMatrix;
+ aMatrix.xx = aMatrix.yy = ARTIFICIAL_ITALIC_MATRIX_XX;
+ aMatrix.xy = ARTIFICIAL_ITALIC_MATRIX_XY;
+ 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;
+}
+
+/* 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 0000000000..ac3c5e15ab
--- /dev/null
+++ b/vcl/unx/generic/glyphs/glyphcache.cxx
@@ -0,0 +1,86 @@
+/* -*- 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 <font/LogicalFontInstance.hxx>
+
+#include <rtl/ustring.hxx>
+#include <sal/log.hxx>
+
+FreetypeManager::FreetypeManager()
+{
+ 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()
+{
+}
+
+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: */