    /* -*- 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/types.h>
    #include <config_folders.h>

    #include <algorithm>
    #include <map>
    #include <memory>
    #include <set>
    #include <string.h>
    #include <svsys.h>
    #include <vector>

    #include <o3tl/lru_map.hxx>
    #include <basegfx/matrix/b2dhommatrixtools.hxx>
    #include <basegfx/polygon/b2dpolygon.hxx>
    #include <i18nlangtag/mslangid.hxx>
    #include <osl/file.hxx>
    #include <osl/process.h>
    #include <rtl/bootstrap.hxx>
    #include <rtl/tencinfo.h>
    #include <sal/log.hxx>
    #include <o3tl/char16_t2wchar_t.hxx>
    #include <tools/helpers.hxx>
    #include <tools/stream.hxx>
    #include <unotools/fontcfg.hxx>
    #include <vcl/settings.hxx>
    #include <vcl/sysdata.hxx>
    #include <vcl/metric.hxx>
    #include <vcl/fontcharmap.hxx>
    #include <vcl/opengl/OpenGLWrapper.hxx>

    #include <fontsubset.hxx>
    #include <outdev.h>
    #include <PhysicalFontCollection.hxx>
    #include <PhysicalFontFace.hxx>
    #include <sft.hxx>
    #include <win/saldata.hxx>
    #include <win/salgdi.h>
    #include <win/winlayout.hxx>
    #include <impfontcharmap.hxx>
    #include <impfontmetricdata.hxx>

    using namespace vcl;

    // GetGlyphOutlineW() seems to be a little slow, and doesn't seem to do its own caching (tested on Windows10).
    // TODO include the font as part of the cache key, then we won't need to clear it on font change
    // The cache limit is set by the rough number of characters needed to read your average Asian newspaper.
    static o3tl::lru_map<sal_GlyphId, tools::Rectangle> g_BoundRectCache(3000);

    static const int MAXFONTHEIGHT = 2048;

    inline FIXED FixedFromDouble( double d )
    {
        const long l = static_cast<long>( d * 65536. );
        return *reinterpret_cast<FIXED const *>(&l);
    }

    inline int IntTimes256FromFixed(FIXED f)
    {
        int nFixedTimes256 = (f.value << 8) + ((f.fract+0x80) >> 8);
        return nFixedTimes256;
    }

    // raw font data with a scoped lifetime
    class RawFontData
    {
    public:
        explicit    RawFontData( HDC, DWORD nTableTag=0 );
        const unsigned char*    get() const { return mpRawBytes.get(); }
        const unsigned char*    steal() { return mpRawBytes.release(); }
        int               size() const { return mnByteCount; }

    private:
        std::unique_ptr<unsigned char[]> mpRawBytes;
        unsigned        mnByteCount;
    };

    RawFontData::RawFontData( HDC hDC, DWORD nTableTag )
    :   mnByteCount( 0 )
    {
        // get required size in bytes
        mnByteCount = ::GetFontData( hDC, nTableTag, 0, nullptr, 0 );
        if (mnByteCount == GDI_ERROR)
            mnByteCount = 0;
        if (!mnByteCount)
            return;

        // allocate the array
        mpRawBytes.reset(new unsigned char[ mnByteCount ]);

        // get raw data in chunks small enough for GetFontData()
        unsigned nRawDataOfs = 0;
        DWORD nMaxChunkSize = 0x100000;
        for(;;)
        {
            // calculate remaining raw data to get
            DWORD nFDGet = mnByteCount - nRawDataOfs;
            if( nFDGet <= 0 )
                break;
            // #i56745# limit GetFontData requests
            if( nFDGet > nMaxChunkSize )
                nFDGet = nMaxChunkSize;
            const DWORD nFDGot = ::GetFontData( hDC, nTableTag, nRawDataOfs,
                mpRawBytes.get() + nRawDataOfs, nFDGet );
            if( !nFDGot )
                break;
            else if( nFDGot != GDI_ERROR )
                nRawDataOfs += nFDGot;
            else
            {
                // was the chunk too big? reduce it
                nMaxChunkSize /= 2;
                if( nMaxChunkSize < 0x10000 )
                    break;
            }
        }

        // cleanup if the raw data is incomplete
        if( nRawDataOfs != mnByteCount )
        {
            mpRawBytes.reset();
            // mnByteCount must correspond to mpRawBytes length
            SAL_WARN( "vcl", "Raw data of font is incomplete: " << nRawDataOfs << " byte(s) found whereas " << mnByteCount << " byte(s) expected!" );
            mnByteCount = 0;
        }
    }

    // platform specific font substitution hooks for glyph fallback enhancement

    class WinPreMatchFontSubstititution
    :    public ImplPreMatchFontSubstitution
    {
    public:
        bool FindFontSubstitute(FontSelectPattern&) const override;
    };

    class WinGlyphFallbackSubstititution
    :    public ImplGlyphFallbackFontSubstitution
    {
    public:
        explicit WinGlyphFallbackSubstititution()
            : mhDC(GetDC(nullptr))
        {
        };

        ~WinGlyphFallbackSubstititution() override
        {
            ReleaseDC(nullptr, mhDC);
        };

        bool FindFontSubstitute(FontSelectPattern&, LogicalFontInstance* pLogicalFont, OUString& rMissingChars) const override;
    private:
        HDC mhDC;
        bool HasMissingChars(PhysicalFontFace*, OUString& rMissingChars) const;
    };

    // does a font face hold the given missing characters?
    bool WinGlyphFallbackSubstititution::HasMissingChars(PhysicalFontFace* pFace, OUString& rMissingChars) const
    {
        WinFontFace* pWinFont = static_cast< WinFontFace* >(pFace);
        FontCharMapRef xFontCharMap = pWinFont->GetFontCharMap();
        if( !xFontCharMap.is() )
        {
            // construct a Size structure as the parameter of constructor of class FontSelectPattern
            const Size aSize( pFace->GetWidth(), pFace->GetHeight() );
            // create a FontSelectPattern object for getting s LOGFONT
            const FontSelectPattern aFSD( *pFace, aSize, static_cast<float>(aSize.Height()), 0, false );
            // construct log font
            LOGFONTW aLogFont;
            ImplGetLogFontFromFontSelect( mhDC, aFSD, pFace, aLogFont );

            // create HFONT from log font
            HFONT hNewFont = ::CreateFontIndirectW( &aLogFont );
            // select the new font into device
            HFONT hOldFont = ::SelectFont( mhDC, hNewFont );

            // read CMAP table to update their xFontCharMap
            pWinFont->UpdateFromHDC( mhDC );

            // cleanup temporary font
            ::SelectFont( mhDC, hOldFont );
            ::DeleteFont( hNewFont );

            // get the new charmap
            xFontCharMap = pWinFont->GetFontCharMap();
        }

        // avoid fonts with unknown CMAP subtables for glyph fallback
        if( !xFontCharMap.is() || xFontCharMap->IsDefaultMap() )
            return false;

        int nMatchCount = 0;
        std::vector<sal_UCS4> rRemainingCodes;
        const sal_Int32 nStrLen = rMissingChars.getLength();
        sal_Int32 nStrIdx = 0;
        while (nStrIdx < nStrLen)
        {
            const sal_UCS4 uChar = rMissingChars.iterateCodePoints( &nStrIdx );
            if (xFontCharMap->HasChar(uChar))
                nMatchCount++;
            else
                rRemainingCodes.push_back(uChar);
        }

        xFontCharMap = nullptr;

        if (nMatchCount > 0)
            rMissingChars = OUString(rRemainingCodes.data(), rRemainingCodes.size());

        return nMatchCount > 0;
    }

    namespace
    {
        //used by 2-level font fallback
        PhysicalFontFamily* findDevFontListByLocale(const PhysicalFontCollection &rFontCollection,
                                                    const LanguageTag& rLanguageTag )
        {
            // get the default font for a specified locale
            const utl::DefaultFontConfiguration& rDefaults = utl::DefaultFontConfiguration::get();
            const OUString aDefault = rDefaults.getUserInterfaceFont(rLanguageTag);
            return rFontCollection.FindFontFamilyByTokenNames(aDefault);
        }
    }

    // These are Win 3.1 bitmap fonts using "FON" font format
    // which is not supported with DirectWrite so let's substitute them
    // with a font that is supported and always available.
    // Based on:
    // https://dxr.mozilla.org/mozilla-esr10/source/gfx/thebes/gfxDWriteFontList.cpp#1057
    static const std::map<OUString, OUString> aBitmapFontSubs =
    {
        { "MS Sans Serif", "Microsoft Sans Serif" },
        { "MS Serif",      "Times New Roman" },
        { "Small Fonts",   "Arial" },
        { "Courier",       "Courier New" },
        { "Roman",         "Times New Roman" },
        { "Script",        "Mistral" }
    };

    // TODO: See if Windows have API that we can use here to improve font fallback.
    bool WinPreMatchFontSubstititution::FindFontSubstitute(FontSelectPattern& rFontSelData) const
    {
        if (rFontSelData.IsSymbolFont() || IsStarSymbol(rFontSelData.maSearchName))
            return false;

        for (const auto& aSub : aBitmapFontSubs)
        {
            if (rFontSelData.maSearchName == GetEnglishSearchFontName(aSub.first))
            {
                rFontSelData.maSearchName = aSub.second;
                return true;
            }
        }

        return false;
    }

    // find a fallback font for missing characters
    // TODO: should stylistic matches be searched and preferred?
    bool WinGlyphFallbackSubstititution::FindFontSubstitute(FontSelectPattern& rFontSelData, LogicalFontInstance* /*pLogicalFont*/, OUString& rMissingChars) const
    {
        // guess a locale matching to the missing chars
        LanguageType eLang = rFontSelData.meLanguage;
        LanguageTag aLanguageTag( eLang);

        // fall back to default UI locale if the font language is inconclusive
        if( eLang == LANGUAGE_DONTKNOW )
            aLanguageTag = Application::GetSettings().GetUILanguageTag();

        // first level fallback:
        // try use the locale specific default fonts defined in VCL.xcu
        const PhysicalFontCollection* pFontCollection = ImplGetSVData()->maGDIData.mxScreenFontList.get();
        PhysicalFontFamily* pFontFamily = findDevFontListByLocale(*pFontCollection, aLanguageTag);
        if( pFontFamily )
        {
            PhysicalFontFace* pFace = pFontFamily->FindBestFontFace( rFontSelData );
            if( HasMissingChars( pFace, rMissingChars ) )
            {
                rFontSelData.maSearchName = pFontFamily->GetSearchName();
                return true;
            }
        }

        // are the missing characters symbols?
        pFontFamily = pFontCollection->FindFontFamilyByAttributes( ImplFontAttrs::Symbol,
                                                         rFontSelData.GetWeight(),
                                                         rFontSelData.GetWidthType(),
                                                         rFontSelData.GetItalic(),
                                                         rFontSelData.maSearchName );
        if( pFontFamily )
        {
            PhysicalFontFace* pFace = pFontFamily->FindBestFontFace( rFontSelData );
            if( HasMissingChars( pFace, rMissingChars ) )
            {
                rFontSelData.maSearchName = pFontFamily->GetSearchName();
                return true;
            }
        }

        // last level fallback, check each font type face one by one
        std::unique_ptr<ImplDeviceFontList> pTestFontList = pFontCollection->GetDeviceFontList();
        // limit the count of fonts to be checked to prevent hangs
        static const int MAX_GFBFONT_COUNT = 600;
        int nTestFontCount = pTestFontList->Count();
        if( nTestFontCount > MAX_GFBFONT_COUNT )
            nTestFontCount = MAX_GFBFONT_COUNT;

        bool bFound = false;
        for( int i = 0; i < nTestFontCount; ++i )
        {
            PhysicalFontFace* pFace = pTestFontList->Get( i );
            bFound = HasMissingChars( pFace, rMissingChars );
            if( !bFound )
                continue;
            rFontSelData.maSearchName = pFace->GetFamilyName();
            break;
        }

        return bFound;
    }

    struct ImplEnumInfo
    {
        HDC                 mhDC;
        PhysicalFontCollection* mpList;
        OUString*           mpName;
        LOGFONTW*           mpLogFont;
        bool                mbPrinter;
        int                 mnFontCount;
    };

    static rtl_TextEncoding ImplCharSetToSal( BYTE nCharSet )
    {
        rtl_TextEncoding eTextEncoding;

        if ( nCharSet == OEM_CHARSET )
        {
            UINT nCP = static_cast<sal_uInt16>(GetOEMCP());
            switch ( nCP )
            {
                // It is unclear why these two (undefined?) code page numbers are
                // handled specially here:
                case 1004:  eTextEncoding = RTL_TEXTENCODING_MS_1252; break;
                case 65400: eTextEncoding = RTL_TEXTENCODING_SYMBOL; break;
                default:
                    eTextEncoding = rtl_getTextEncodingFromWindowsCodePage(nCP);
                    break;
            }
        }
        else
        {
            if( nCharSet )
                eTextEncoding = rtl_getTextEncodingFromWindowsCharset( nCharSet );
            else
                eTextEncoding = RTL_TEXTENCODING_UNICODE;
        }

        return eTextEncoding;
    }

    static FontFamily ImplFamilyToSal( BYTE nFamily )
    {
        switch ( nFamily & 0xF0 )
        {
            case FF_DECORATIVE:
                return FAMILY_DECORATIVE;

            case FF_MODERN:
                return FAMILY_MODERN;

            case FF_ROMAN:
                return FAMILY_ROMAN;

            case FF_SCRIPT:
                return FAMILY_SCRIPT;

            case FF_SWISS:
                return FAMILY_SWISS;

            default:
                break;
        }

        return FAMILY_DONTKNOW;
    }

    static BYTE ImplFamilyToWin( FontFamily eFamily )
    {
        switch ( eFamily )
        {
            case FAMILY_DECORATIVE:
                return FF_DECORATIVE;

            case FAMILY_MODERN:
                return FF_MODERN;

            case FAMILY_ROMAN:
                return FF_ROMAN;

            case FAMILY_SCRIPT:
                return FF_SCRIPT;

            case FAMILY_SWISS:
                return FF_SWISS;

            case FAMILY_SYSTEM:
                return FF_SWISS;

            default:
                break;
        }

        return FF_DONTCARE;
    }

    static FontWeight ImplWeightToSal( int nWeight )
    {
        if ( nWeight <= FW_THIN )
            return WEIGHT_THIN;
        else if ( nWeight <= FW_ULTRALIGHT )
            return WEIGHT_ULTRALIGHT;
        else if ( nWeight <= FW_LIGHT )
            return WEIGHT_LIGHT;
        else if ( nWeight < FW_MEDIUM )
            return WEIGHT_NORMAL;
        else if ( nWeight == FW_MEDIUM )
            return WEIGHT_MEDIUM;
        else if ( nWeight <= FW_SEMIBOLD )
            return WEIGHT_SEMIBOLD;
        else if ( nWeight <= FW_BOLD )
            return WEIGHT_BOLD;
        else if ( nWeight <= FW_ULTRABOLD )
            return WEIGHT_ULTRABOLD;
        else
            return WEIGHT_BLACK;
    }

    static int ImplWeightToWin( FontWeight eWeight )
    {
        switch ( eWeight )
        {
            case WEIGHT_THIN:
                return FW_THIN;

            case WEIGHT_ULTRALIGHT:
                return FW_ULTRALIGHT;

            case WEIGHT_LIGHT:
                return FW_LIGHT;

            case WEIGHT_SEMILIGHT:
            case WEIGHT_NORMAL:
                return FW_NORMAL;

            case WEIGHT_MEDIUM:
                return FW_MEDIUM;

            case WEIGHT_SEMIBOLD:
                return FW_SEMIBOLD;

            case WEIGHT_BOLD:
                return FW_BOLD;

            case WEIGHT_ULTRABOLD:
                return FW_ULTRABOLD;

            case WEIGHT_BLACK:
                return FW_BLACK;

            default:
                break;
        }

        return 0;
    }

    inline FontPitch ImplLogPitchToSal( BYTE nPitch )
    {
        if ( nPitch & FIXED_PITCH )
            return PITCH_FIXED;
        else
            return PITCH_VARIABLE;
    }

    inline FontPitch ImplMetricPitchToSal( BYTE nPitch )
    {
        // Grrrr! See NT help
        if ( !(nPitch & TMPF_FIXED_PITCH) )
            return PITCH_FIXED;
        else
            return PITCH_VARIABLE;
    }

    inline BYTE ImplPitchToWin( FontPitch ePitch )
    {
        if ( ePitch == PITCH_FIXED )
            return FIXED_PITCH;
        else if ( ePitch == PITCH_VARIABLE )
            return VARIABLE_PITCH;
        else
            return DEFAULT_PITCH;
    }

    static FontAttributes WinFont2DevFontAttributes( const ENUMLOGFONTEXW& rEnumFont,
        const NEWTEXTMETRICW& rMetric)
    {
        FontAttributes aDFA;

        const LOGFONTW rLogFont = rEnumFont.elfLogFont;

        // get font face attributes
        aDFA.SetFamilyType(ImplFamilyToSal( rLogFont.lfPitchAndFamily ));
        aDFA.SetWidthType(WIDTH_DONTKNOW);
        aDFA.SetWeight(ImplWeightToSal( rLogFont.lfWeight ));
        aDFA.SetItalic((rLogFont.lfItalic) ? ITALIC_NORMAL : ITALIC_NONE);
        aDFA.SetPitch(ImplLogPitchToSal( rLogFont.lfPitchAndFamily ));
        aDFA.SetSymbolFlag(rLogFont.lfCharSet == SYMBOL_CHARSET);

        // get the font face name
        aDFA.SetFamilyName(o3tl::toU(rLogFont.lfFaceName));

        // use the face's style name only if it looks reasonable
        const wchar_t* pStyleName = rEnumFont.elfStyle;
        const wchar_t* pEnd = pStyleName + sizeof(rEnumFont.elfStyle)/sizeof(*rEnumFont.elfStyle);
        const wchar_t* p = pStyleName;
        for(; *p && (p < pEnd); ++p )
            if( *p < 0x0020 )
                break;
        if( p < pEnd )
            aDFA.SetStyleName(o3tl::toU(pStyleName));

        // heuristics for font quality
        // -   opentypeTT > truetype
        aDFA.SetQuality( 0 );
        if( rMetric.tmPitchAndFamily & TMPF_TRUETYPE )
            aDFA.IncreaseQualityBy( 50 );
        if( 0 != (rMetric.ntmFlags & (NTM_TT_OPENTYPE | NTM_PS_OPENTYPE)) )
            aDFA.IncreaseQualityBy( 10 );

        // TODO: add alias names
        return aDFA;
    }

    static rtl::Reference<WinFontFace> ImplLogMetricToDevFontDataW( const ENUMLOGFONTEXW* pLogFont,
                                             const NEWTEXTMETRICW* pMetric,
                                             DWORD nFontType )
    {
        int nHeight = 0;
        if ( nFontType & RASTER_FONTTYPE )
            nHeight = pMetric->tmHeight - pMetric->tmInternalLeading;

        rtl::Reference<WinFontFace> pData = new WinFontFace(
            WinFont2DevFontAttributes(*pLogFont, *pMetric),
            nHeight,
            pLogFont->elfLogFont.lfCharSet,
            pMetric->tmPitchAndFamily );

        return pData;
    }

    void ImplSalLogFontToFontW( HDC hDC, const LOGFONTW& rLogFont, Font& rFont )
    {
        OUString aFontName( o3tl::toU(rLogFont.lfFaceName) );
        if (!aFontName.isEmpty())
        {
            rFont.SetFamilyName( aFontName );
            rFont.SetCharSet( ImplCharSetToSal( rLogFont.lfCharSet ) );
            rFont.SetFamily( ImplFamilyToSal( rLogFont.lfPitchAndFamily ) );
            rFont.SetPitch( ImplLogPitchToSal( rLogFont.lfPitchAndFamily ) );
            rFont.SetWeight( ImplWeightToSal( rLogFont.lfWeight ) );

            long nFontHeight = rLogFont.lfHeight;
            if ( nFontHeight < 0 )
                nFontHeight = -nFontHeight;
            long nDPIY = GetDeviceCaps( hDC, LOGPIXELSY );
            if( !nDPIY )
                nDPIY = 600;
            nFontHeight *= 72;
            nFontHeight += nDPIY/2;
            nFontHeight /= nDPIY;
            rFont.SetFontSize( Size( 0, nFontHeight ) );
            rFont.SetOrientation( static_cast<short>(rLogFont.lfEscapement) );
            if ( rLogFont.lfItalic )
                rFont.SetItalic( ITALIC_NORMAL );
            else
                rFont.SetItalic( ITALIC_NONE );
            if ( rLogFont.lfUnderline )
                rFont.SetUnderline( LINESTYLE_SINGLE );
            else
                rFont.SetUnderline( LINESTYLE_NONE );
            if ( rLogFont.lfStrikeOut )
                rFont.SetStrikeout( STRIKEOUT_SINGLE );
            else
                rFont.SetStrikeout( STRIKEOUT_NONE );
        }
    }

    WinFontFace::WinFontFace( const FontAttributes& rDFS,
        int nHeight, BYTE eWinCharSet, BYTE nPitchAndFamily )
    :   PhysicalFontFace( rDFS ),
        mnId( 0 ),
        mbFontCapabilitiesRead( false ),
        mxUnicodeMap( nullptr ),
        meWinCharSet( eWinCharSet ),
        mnPitchAndFamily( nPitchAndFamily ),
        mbAliasSymbolsHigh( false ),
        mbAliasSymbolsLow( false )
    {
        SetBitmapSize( 0, nHeight );

        if( eWinCharSet == SYMBOL_CHARSET )
        {
            if( (nPitchAndFamily & TMPF_TRUETYPE) != 0 )
            {
                // truetype fonts need their symbols as U+F0xx
                mbAliasSymbolsHigh = true;
            }
            else if( (nPitchAndFamily & (TMPF_VECTOR|TMPF_DEVICE))
                                     == (TMPF_VECTOR|TMPF_DEVICE) )
            {
                // scalable device fonts (e.g. builtin printer fonts)
                // need their symbols as U+00xx
                mbAliasSymbolsLow  = true;
            }
            else if( (nPitchAndFamily & (TMPF_VECTOR|TMPF_TRUETYPE)) == 0 )
            {
                // bitmap fonts need their symbols as U+F0xx
                mbAliasSymbolsHigh = true;
            }
        }
    }

    WinFontFace::~WinFontFace()
    {
        mxUnicodeMap.clear();
    }

    sal_IntPtr WinFontFace::GetFontId() const
    {
        return mnId;
    }

    rtl::Reference<LogicalFontInstance> WinFontFace::CreateFontInstance(const FontSelectPattern& rFSD) const
    {
        return new WinFontInstance(*this, rFSD);
    }

    static inline DWORD CalcTag( const char p[5]) { return (p[0]+(p[1]<<8)+(p[2]<<16)+(p[3]<<24)); }

    void WinFontFace::UpdateFromHDC( HDC hDC ) const
    {
        // short circuit if already initialized
        if( mxUnicodeMap.is() )
            return;

        ReadCmapTable( hDC );
        GetFontCapabilities( hDC );
    }

    FontCharMapRef WinFontFace::GetFontCharMap() const
    {
        return mxUnicodeMap;
    }

    bool WinFontFace::GetFontCapabilities(vcl::FontCapabilities &rFontCapabilities) const
    {
        rFontCapabilities = maFontCapabilities;
        return rFontCapabilities.oUnicodeRange || rFontCapabilities.oCodePageRange;
    }

    void WinFontFace::ReadCmapTable( HDC hDC ) const
    {
        if( mxUnicodeMap.is() )
            return;

        bool bIsSymbolFont = (meWinCharSet == SYMBOL_CHARSET);
        // get the CMAP table from the font which is selected into the DC
        const DWORD nCmapTag = CalcTag( "cmap" );
        const RawFontData aRawFontData( hDC, nCmapTag );
        // parse the CMAP table if available
        if( aRawFontData.get() ) {
            CmapResult aResult;
            ParseCMAP( aRawFontData.get(), aRawFontData.size(), aResult );
            aResult.mbSymbolic = bIsSymbolFont;
            if( aResult.mnRangeCount > 0 )
            {
                FontCharMapRef pUnicodeMap(new FontCharMap(aResult));
                mxUnicodeMap = pUnicodeMap;
            }
        }

        if( !mxUnicodeMap.is() )
        {
            mxUnicodeMap = FontCharMap::GetDefaultMap( bIsSymbolFont );
        }
    }

    void WinFontFace::GetFontCapabilities( HDC hDC ) const
    {
        // read this only once per font
        if( mbFontCapabilitiesRead )
            return;

        mbFontCapabilitiesRead = true;

        // OS/2 table
        const DWORD OS2Tag = CalcTag( "OS/2" );
        DWORD nLength = ::GetFontData( hDC, OS2Tag, 0, nullptr, 0 );
        if( (nLength != GDI_ERROR) && nLength )
        {
            std::vector<unsigned char> aTable( nLength );
            unsigned char* pTable = &aTable[0];
            ::GetFontData( hDC, OS2Tag, 0, pTable, nLength );
            vcl::getTTCoverage(maFontCapabilities.oUnicodeRange, maFontCapabilities.oCodePageRange, pTable, nLength);
        }
    }

    void WinSalGraphics::SetTextColor( Color nColor )
    {
        COLORREF aCol = PALETTERGB( nColor.GetRed(),
                                    nColor.GetGreen(),
                                    nColor.GetBlue() );

        if( !mbPrinter &&
            GetSalData()->mhDitherPal &&
            ImplIsSysColorEntry( nColor ) )
        {
            aCol = PALRGB_TO_RGB( aCol );
        }

        ::SetTextColor( getHDC(), aCol );
    }

    int CALLBACK SalEnumQueryFontProcExW( const LOGFONTW*,
                                          const TEXTMETRICW*,
                                          DWORD, LPARAM lParam )
    {
        *reinterpret_cast<bool*>(lParam) = true;
        return 0;
    }

    void ImplGetLogFontFromFontSelect( HDC hDC,
                                       const FontSelectPattern& rFont,
                                       const PhysicalFontFace* pFontFace,
                                       LOGFONTW& rLogFont )
    {
        OUString aName;
        if (pFontFace)
            aName = pFontFace->GetFamilyName();
        else
            aName = rFont.GetFamilyName().getToken( 0, ';' );

        UINT nNameLen = aName.getLength();
        if ( nNameLen > (sizeof( rLogFont.lfFaceName )/sizeof( wchar_t ))-1 )
            nNameLen = (sizeof( rLogFont.lfFaceName )/sizeof( wchar_t ))-1;
        memcpy( rLogFont.lfFaceName, aName.getStr(), nNameLen*sizeof( wchar_t ) );
        rLogFont.lfFaceName[nNameLen] = 0;

        if  (pFontFace)
        {
            const WinFontFace* pWinFontData = static_cast<const WinFontFace*>(pFontFace);
            rLogFont.lfCharSet = pWinFontData->GetCharSet();
            rLogFont.lfPitchAndFamily = pWinFontData->GetPitchAndFamily();
        }
        else
        {
            rLogFont.lfCharSet = rFont.IsSymbolFont() ? SYMBOL_CHARSET : DEFAULT_CHARSET;
            rLogFont.lfPitchAndFamily = ImplPitchToWin( rFont.GetPitch() )
                                      | ImplFamilyToWin( rFont.GetFamilyType() );
        }

        static BYTE nDefaultQuality = NONANTIALIASED_QUALITY;
        if (nDefaultQuality == NONANTIALIASED_QUALITY)
        {
            if (OpenGLWrapper::isVCLOpenGLEnabled())
                nDefaultQuality = ANTIALIASED_QUALITY;
            else
                nDefaultQuality = DEFAULT_QUALITY;
        }

        rLogFont.lfWeight          = ImplWeightToWin( rFont.GetWeight() );
        rLogFont.lfHeight          = static_cast<LONG>(-rFont.mnHeight);
        rLogFont.lfWidth           = static_cast<LONG>(rFont.mnWidth);
        rLogFont.lfUnderline       = 0;
        rLogFont.lfStrikeOut       = 0;
        rLogFont.lfItalic          = BYTE(rFont.GetItalic() != ITALIC_NONE);
        rLogFont.lfEscapement      = rFont.mnOrientation;
        rLogFont.lfOrientation     = rLogFont.lfEscapement;
        rLogFont.lfClipPrecision   = CLIP_DEFAULT_PRECIS;
        rLogFont.lfQuality         = nDefaultQuality;
        rLogFont.lfOutPrecision    = OUT_TT_PRECIS;
        if ( rFont.mnOrientation )
            rLogFont.lfClipPrecision |= CLIP_LH_ANGLES;

        // disable antialiasing if requested
        if ( rFont.mbNonAntialiased )
            rLogFont.lfQuality = NONANTIALIASED_QUALITY;

        // select vertical mode if requested and available
        if ( rFont.mbVertical && nNameLen )
        {
            // vertical fonts start with an '@'
            memmove( &rLogFont.lfFaceName[1], &rLogFont.lfFaceName[0],
                sizeof(rLogFont.lfFaceName)-sizeof(rLogFont.lfFaceName[0]) );
            rLogFont.lfFaceName[0] = '@';

            // check availability of vertical mode for this font
            bool bAvailable = false;
            EnumFontFamiliesExW( hDC, &rLogFont, SalEnumQueryFontProcExW,
                             reinterpret_cast<LPARAM>(&bAvailable), 0 );

            if( !bAvailable )
            {
                // restore non-vertical name if not vertical mode isn't available
                memcpy( &rLogFont.lfFaceName[0], aName.getStr(), nNameLen*sizeof(wchar_t) );
                if( nNameLen < LF_FACESIZE )
                    rLogFont.lfFaceName[nNameLen] = '\0';
                // keep it upright and create the font for sideway glyphs later.
                rLogFont.lfEscapement = rLogFont.lfEscapement - 2700;
                rLogFont.lfOrientation = rLogFont.lfEscapement;
            }
        }
    }

    HFONT WinSalGraphics::ImplDoSetFont(FontSelectPattern const & i_rFont,
                                        const PhysicalFontFace * i_pFontFace,
                                        float& o_rFontScale,
                                        HFONT& o_rOldFont)
    {
        // clear the cache on font change
        g_BoundRectCache.clear();
        HFONT hNewFont = nullptr;

        LOGFONTW aLogFont;
        ImplGetLogFontFromFontSelect( getHDC(), i_rFont, i_pFontFace, aLogFont );

        // #i47675# limit font requests to MAXFONTHEIGHT
        // TODO: share MAXFONTHEIGHT font instance
        if( (-aLogFont.lfHeight <= MAXFONTHEIGHT)
        &&  (+aLogFont.lfWidth <= MAXFONTHEIGHT) )
        {
            o_rFontScale = 1.0;
        }
        else if( -aLogFont.lfHeight >= +aLogFont.lfWidth )
        {
            o_rFontScale = -aLogFont.lfHeight / float(MAXFONTHEIGHT);
            aLogFont.lfHeight = -MAXFONTHEIGHT;
            aLogFont.lfWidth = FRound( aLogFont.lfWidth / o_rFontScale );
        }
        else // #i95867# also limit font widths
        {
            o_rFontScale = +aLogFont.lfWidth / float(MAXFONTHEIGHT);
            aLogFont.lfWidth = +MAXFONTHEIGHT;
            aLogFont.lfHeight = FRound( aLogFont.lfHeight / o_rFontScale );
        }
        hNewFont = ::CreateFontIndirectW( &aLogFont );

        HDC hdcScreen = nullptr;
        if( mbVirDev )
            // only required for virtual devices, see below for details
            hdcScreen = GetDC(nullptr);
        if( hdcScreen )
        {
            // select font into screen hdc first to get an antialiased font
            // and instantly restore the default font!
            // see knowledge base article 305290:
            // "PRB: Fonts Not Drawn Antialiased on Device Context for DirectDraw Surface"
            SelectFont( hdcScreen, SelectFont( hdcScreen , hNewFont ) );
        }
        o_rOldFont = ::SelectFont( getHDC(), hNewFont );

        TEXTMETRICW aTextMetricW;
        if( !::GetTextMetricsW( getHDC(), &aTextMetricW ) )
        {
            // the selected font doesn't work => try a replacement
            // TODO: use its font fallback instead
            lstrcpynW( aLogFont.lfFaceName, L"Courier New", 12 );
            aLogFont.lfPitchAndFamily = FIXED_PITCH;
            HFONT hNewFont2 = CreateFontIndirectW( &aLogFont );
            SelectFont( getHDC(), hNewFont2 );
            DeleteFont( hNewFont );
            hNewFont = hNewFont2;
        }

        if( hdcScreen )
            ::ReleaseDC( nullptr, hdcScreen );

        return hNewFont;
    }

    void WinSalGraphics::SetFont(LogicalFontInstance* pFont, int nFallbackLevel)
    {
        // return early if there is no new font
        if( !pFont )
        {
            if (!mpWinFontEntry[nFallbackLevel].is())
                return;

            // select original DC font
            assert(mhDefFont);
            ::SelectFont(getHDC(), mhDefFont);
            mhDefFont = nullptr;

            // release no longer referenced font handles
            for( int i = nFallbackLevel; i < MAX_FALLBACK; ++i )
                mpWinFontEntry[i] = nullptr;
            return;
        }

        WinFontInstance *pFontInstance = static_cast<WinFontInstance*>(pFont);
        mpWinFontEntry[ nFallbackLevel ] = pFontInstance;

        HFONT hOldFont = nullptr;
        HFONT hNewFont = pFontInstance->GetHFONT();
        if (!hNewFont)
        {
            pFontInstance->SetGraphics(this);
            hNewFont = pFontInstance->GetHFONT();
        }
        hOldFont = ::SelectFont(getHDC(), hNewFont);

        // keep default font
        if( !mhDefFont )
            mhDefFont = hOldFont;
        else
        {
            // release no longer referenced font handles
            for( int i = nFallbackLevel + 1; i < MAX_FALLBACK && mpWinFontEntry[i].is(); ++i )
                mpWinFontEntry[i] = nullptr;
        }

        // now the font is live => update font face
        const WinFontFace* pFontFace = static_cast<const WinFontFace*>(pFontInstance->GetFontFace());
        pFontFace->UpdateFromHDC(getHDC());
    }

    void WinSalGraphics::GetFontMetric( ImplFontMetricDataRef& rxFontMetric, int nFallbackLevel )
    {
        // temporarily change the HDC to the font in the fallback level
        rtl::Reference<WinFontInstance> pFontInstance = mpWinFontEntry[nFallbackLevel];
        const HFONT hOldFont = SelectFont(getHDC(), pFontInstance->GetHFONT());

        wchar_t aFaceName[LF_FACESIZE+60];
        if( GetTextFaceW( getHDC(), SAL_N_ELEMENTS(aFaceName), aFaceName ) )
            rxFontMetric->SetFamilyName(o3tl::toU(aFaceName));

        const DWORD nHheaTag = CalcTag("hhea");
        const DWORD nOS2Tag = CalcTag("OS/2");
        const RawFontData aHheaRawData(getHDC(), nHheaTag);
        const RawFontData aOS2RawData(getHDC(), nOS2Tag);

        rxFontMetric->SetMinKashida(pFontInstance->GetKashidaWidth());

        // get the font metric
        OUTLINETEXTMETRICW aOutlineMetric;
        const bool bOK = GetOutlineTextMetricsW(getHDC(), sizeof(aOutlineMetric), &aOutlineMetric);
        // restore the HDC to the font in the base level
        SelectFont( getHDC(), hOldFont );
        if( !bOK )
            return;

        TEXTMETRICW aWinMetric = aOutlineMetric.otmTextMetrics;

        // device independent font attributes
        rxFontMetric->SetFamilyType(ImplFamilyToSal( aWinMetric.tmPitchAndFamily ));
        rxFontMetric->SetSymbolFlag(aWinMetric.tmCharSet == SYMBOL_CHARSET);
        rxFontMetric->SetWeight(ImplWeightToSal( aWinMetric.tmWeight ));
        rxFontMetric->SetPitch(ImplMetricPitchToSal( aWinMetric.tmPitchAndFamily ));
        rxFontMetric->SetItalic(aWinMetric.tmItalic ? ITALIC_NORMAL : ITALIC_NONE);
        rxFontMetric->SetSlant( 0 );

        // transformation dependent font metrics
        rxFontMetric->SetWidth(static_cast<int>(pFontInstance->GetScale() * aWinMetric.tmAveCharWidth));

        const std::vector<uint8_t> rHhea(aHheaRawData.get(), aHheaRawData.get() + aHheaRawData.size());
        const std::vector<uint8_t> rOS2(aOS2RawData.get(), aOS2RawData.get() + aOS2RawData.size());
        rxFontMetric->ImplCalcLineSpacing(rHhea, rOS2, aOutlineMetric.otmEMSquare);
    }

    const FontCharMapRef WinSalGraphics::GetFontCharMap() const
    {
        if (!mpWinFontEntry[0])
        {
            FontCharMapRef xDefFontCharMap( new FontCharMap() );
            return xDefFontCharMap;
        }
        return static_cast<const WinFontFace*>(mpWinFontEntry[0]->GetFontFace())->GetFontCharMap();
    }

    bool WinSalGraphics::GetFontCapabilities(vcl::FontCapabilities &rFontCapabilities) const
    {
        if (!mpWinFontEntry[0])
            return false;
        return static_cast<const WinFontFace*>(mpWinFontEntry[0]->GetFontFace())->GetFontCapabilities(rFontCapabilities);
    }

    int CALLBACK SalEnumFontsProcExW( const LOGFONTW* lpelfe,
                                      const TEXTMETRICW* lpntme,
                                      DWORD nFontType, LPARAM lParam )
    {
        ENUMLOGFONTEXW const * pLogFont
            = reinterpret_cast<ENUMLOGFONTEXW const *>(lpelfe);
        NEWTEXTMETRICEXW const * pMetric
            = reinterpret_cast<NEWTEXTMETRICEXW const *>(lpntme);
        ImplEnumInfo* pInfo = reinterpret_cast<ImplEnumInfo*>(lParam);
        if ( !pInfo->mpName )
        {
            // Ignore vertical fonts
            if ( pLogFont->elfLogFont.lfFaceName[0] != '@' )
            {
                OUString aName = o3tl::toU(pLogFont->elfLogFont.lfFaceName);
                pInfo->mpName = &aName;
                memcpy(pInfo->mpLogFont->lfFaceName, pLogFont->elfLogFont.lfFaceName, (aName.getLength()+1)*sizeof(wchar_t));
                pInfo->mpLogFont->lfCharSet = pLogFont->elfLogFont.lfCharSet;
                EnumFontFamiliesExW(pInfo->mhDC, pInfo->mpLogFont, SalEnumFontsProcExW,
                                    reinterpret_cast<LPARAM>(pInfo), 0);
                pInfo->mpLogFont->lfFaceName[0] = '\0';
                pInfo->mpLogFont->lfCharSet = DEFAULT_CHARSET;
                pInfo->mpName = nullptr;
            }
        }
        else
        {
            // Ignore non-device fonts on printers.
            if (pInfo->mbPrinter)
            {
                if ((nFontType & RASTER_FONTTYPE) && !(nFontType & DEVICE_FONTTYPE))
                {
                    SAL_WARN("vcl.fonts", "Unsupported printer font ignored: " << OUString(o3tl::toU(pLogFont->elfLogFont.lfFaceName)));
                    return 1;
                }
            }
            // Only SFNT fonts are supported, ignore anything else.
            else if (!(nFontType & TRUETYPE_FONTTYPE) &&
                     !(pMetric->ntmTm.ntmFlags & NTM_PS_OPENTYPE) &&
                     !(pMetric->ntmTm.ntmFlags & NTM_TT_OPENTYPE))
            {
                SAL_WARN("vcl.fonts", "Unsupported font ignored: " << OUString(o3tl::toU(pLogFont->elfLogFont.lfFaceName)));
                return 1;
            }

            rtl::Reference<WinFontFace> pData = ImplLogMetricToDevFontDataW( pLogFont, &(pMetric->ntmTm), nFontType );
            pData->SetFontId( sal_IntPtr( pInfo->mnFontCount++ ) );

            pInfo->mpList->Add( pData.get() );
            SAL_WARN("vcl.fonts", "SalEnumFontsProcExW: font added: " << pData->GetFamilyName() << " " << pData->GetStyleName());
        }

        return 1;
    }

    struct TempFontItem
    {
        OUString maFontFilePath;
        OUString maResourcePath;
        TempFontItem* mpNextItem;
    };

    bool ImplAddTempFont( SalData& rSalData, const OUString& rFontFileURL )
    {
        int nRet = 0;
        OUString aUSytemPath;
        OSL_VERIFY( !osl::FileBase::getSystemPathFromFileURL( rFontFileURL, aUSytemPath ) );

        nRet = AddFontResourceExW( o3tl::toW(aUSytemPath.getStr()), FR_PRIVATE, nullptr );

        if ( !nRet )
        {
            static int nCounter = 0;
            wchar_t aFileName[] = L"soAA.fot";
            aFileName[2] = sal::static_int_cast<wchar_t>(L'A' + (15 & (nCounter>>4)));
            aFileName[3] = sal::static_int_cast<wchar_t>(L'A' + (15 & nCounter));
            wchar_t aResourceName[512];
            int const nMaxLen = SAL_N_ELEMENTS(aResourceName) - 16;
            int nLen = GetTempPathW( nMaxLen, aResourceName );
            wcsncpy( aResourceName + nLen, aFileName, SAL_N_ELEMENTS( aResourceName ) - nLen );
            // security: end buffer in any case
            aResourceName[ SAL_N_ELEMENTS(aResourceName)-1 ] = 0;
            DeleteFileW( aResourceName );

            // TODO: font should be private => need to investigate why it doesn't work then
            if( !CreateScalableFontResourceW( 0, aResourceName, o3tl::toW(aUSytemPath.getStr()), nullptr ) )
                return false;
            ++nCounter;

            nRet = AddFontResourceW( aResourceName );
            if( nRet > 0 )
            {
                TempFontItem* pNewItem = new TempFontItem;
                pNewItem->maResourcePath = o3tl::toU( aResourceName );
                pNewItem->maFontFilePath = aUSytemPath;
                pNewItem->mpNextItem = rSalData.mpTempFontItem;
                rSalData.mpTempFontItem = pNewItem;
            }
        }

        return (nRet > 0);
    }

    void ImplReleaseTempFonts( SalData& rSalData )
    {
        int nCount = 0;
        while( TempFontItem* p = rSalData.mpTempFontItem )
        {
            ++nCount;
            if( p->maResourcePath.getLength() )
            {
                const wchar_t* pResourcePath = o3tl::toW(p->maResourcePath.getStr());
                RemoveFontResourceW( pResourcePath );
                DeleteFileW( pResourcePath );
            }
            else
            {
                RemoveFontResourceW( o3tl::toW(p->maFontFilePath.getStr()) );
            }

            rSalData.mpTempFontItem = p->mpNextItem;
            delete p;
        }
    }

    static bool ImplGetFontAttrFromFile( const OUString& rFontFileURL,
        FontAttributes& rDFA )
    {
        OUString aUSytemPath;
        OSL_VERIFY( !osl::FileBase::getSystemPathFromFileURL( rFontFileURL, aUSytemPath ) );

        // get FontAttributes from a *fot file
        // TODO: use GetTTGlobalFontInfo() to access the font directly
        rDFA.SetQuality( 1000 );
        rDFA.SetFamilyType(FAMILY_DONTKNOW);
        rDFA.SetWidthType(WIDTH_DONTKNOW);
        rDFA.SetWeight(WEIGHT_DONTKNOW);
        rDFA.SetItalic(ITALIC_DONTKNOW);
        rDFA.SetPitch(PITCH_DONTKNOW);

        // Create temporary file name
        wchar_t aResourceName[512];
        int nMaxLen = SAL_N_ELEMENTS(aResourceName) - 16;
        int nLen = GetTempPathW( nMaxLen, aResourceName );
        wcsncpy( aResourceName + nLen, L"soAAT.fot", std::max( 0, nMaxLen - nLen ));
        DeleteFileW( aResourceName );

        // Create font resource file (typically with a .fot file name extension).
        CreateScalableFontResourceW( 0, aResourceName, o3tl::toW(aUSytemPath.getStr()), nullptr );

        // Open and read the font resource file
        OUString aFotFileName = o3tl::toU( aResourceName );
        osl::FileBase::getFileURLFromSystemPath( aFotFileName, aFotFileName );
        osl::File aFotFile( aFotFileName );
        osl::FileBase::RC aError = aFotFile.open( osl_File_OpenFlag_Read );
        if( aError != osl::FileBase::E_None )
            return false;

        sal_uInt64  nBytesRead = 0;
        char        aBuffer[4096];
        aFotFile.read( aBuffer, sizeof( aBuffer ), nBytesRead );
        // clean up temporary resource file
        aFotFile.close();
        DeleteFileW( aResourceName );

        // retrieve font family name from byte offset 0x4F6
        sal_uInt64 i = 0x4F6;
        sal_uInt64 nNameOfs = i;
        while( (i < nBytesRead) && (aBuffer[i++] != 0) );
        // skip full name
        while( (i < nBytesRead) && (aBuffer[i++] != 0) );
        // retrieve font style name
        int nStyleOfs = i;
        while( (i < nBytesRead) && (aBuffer[i++] != 0) );
        if( i >= nBytesRead )
            return false;

        // convert byte strings to unicode
        char *pName = aBuffer + nNameOfs;
        rDFA.SetFamilyName(OUString(pName, strlen(pName), osl_getThreadTextEncoding()));
        char *pStyle = aBuffer + nStyleOfs;
        rDFA.SetStyleName(OUString(pStyle, strlen(pStyle), osl_getThreadTextEncoding() ));

        // byte offset 0x4C7: OS2_fsSelection
        const char nFSS = aBuffer[ 0x4C7 ];
        if( nFSS & 0x01 )   // italic
            rDFA.SetItalic(ITALIC_NORMAL);
        //if( nFSS & 0x20 )   // bold
        //   rDFA.meWeight = WEIGHT_BOLD;
        if( nFSS & 0x40 )   // regular
        {
            rDFA.SetWeight(WEIGHT_NORMAL);
            rDFA.SetItalic(ITALIC_NONE);
        }

        // byte offsets 0x4D7/0x4D8: wingdi's FW_WEIGHT
        int nWinWeight = (aBuffer[0x4D7] & 0xFF) + ((aBuffer[0x4D8] & 0xFF) << 8);
        rDFA.SetWeight(ImplWeightToSal( nWinWeight ));

        rDFA.SetSymbolFlag(false);          // TODO
        rDFA.SetPitch(PITCH_DONTKNOW); // TODO

        // byte offset 0x4DE: pitch&family
        rDFA.SetFamilyType(ImplFamilyToSal( aBuffer[0x4DE] ));

        // byte offsets 0x4C8/0x4C9: emunits
        // byte offsets 0x4CE/0x4CF: winascent
        // byte offsets 0x4D0/0x4D1: winascent+windescent-emunits
        // byte offsets 0x4DF/0x4E0: avgwidth

        return true;
    }

    bool WinSalGraphics::AddTempDevFont( PhysicalFontCollection* pFontCollection,
        const OUString& rFontFileURL, const OUString& rFontName )
    {
        SAL_WARN( "vcl.fonts", "WinSalGraphics::AddTempDevFont(): " << rFontFileURL );

        FontAttributes aDFA;
        aDFA.SetFamilyName(rFontName);
        aDFA.SetQuality( 1000 );

        // Retrieve font name from font resource
        if( aDFA.GetFamilyName().isEmpty() )
        {
            ImplGetFontAttrFromFile( rFontFileURL, aDFA );
        }

        if ( aDFA.GetFamilyName().isEmpty() )
            return false;

        // remember temp font for cleanup later
        if( !ImplAddTempFont( *GetSalData(), rFontFileURL ) )
            return false;

        // create matching FontData struct
        aDFA.SetSymbolFlag(false); // TODO: how to know it without accessing the font?
        aDFA.SetFamilyType(FAMILY_DONTKNOW);
        aDFA.SetWidthType(WIDTH_DONTKNOW);
        aDFA.SetWeight(WEIGHT_DONTKNOW);
        aDFA.SetItalic(ITALIC_DONTKNOW);
        aDFA.SetPitch(PITCH_DONTKNOW);

        /*
        // TODO: improve FontAttributes using the "font resource file"
        aDFS.maName = // using "FONTRES:" from file
        if( rFontName != aDFS.maName )
            aDFS.maMapName = aFontName;
        */

        rtl::Reference<WinFontFace> pFontData = new WinFontFace( aDFA, 0,
            sal::static_int_cast<BYTE>(DEFAULT_CHARSET),
            sal::static_int_cast<BYTE>(TMPF_VECTOR|TMPF_TRUETYPE) );
        pFontData->SetFontId( reinterpret_cast<sal_IntPtr>(pFontData.get()) );
        pFontCollection->Add( pFontData.get() );
        return true;
    }

    void WinSalGraphics::GetDevFontList( PhysicalFontCollection* pFontCollection )
    {
        SAL_WARN( "vcl.fonts", "WinSalGraphics::GetDevFontList(): enter" );

        // make sure all fonts are registered at least temporarily
        static bool bOnce = true;
        if( bOnce )
        {
            bOnce = false;

            // determine font path
            // since we are only interested in fonts that could not be
            // registered before because of missing administration rights
            // only the font path of the user installation is needed
            OUString aPath("$BRAND_BASE_DIR");
            rtl_bootstrap_expandMacros(&aPath.pData);

            // collect fonts in font path that could not be registered
            osl::Directory aFontDir(aPath + "/" LIBO_SHARE_FOLDER "/fonts/truetype");
            osl::FileBase::RC rcOSL = aFontDir.open();
            if( rcOSL == osl::FileBase::E_None )
            {
                osl::DirectoryItem aDirItem;

                while( aFontDir.getNextItem( aDirItem, 10 ) == osl::FileBase::E_None )
                {
                    osl::FileStatus aFileStatus( osl_FileStatus_Mask_FileURL );
                    rcOSL = aDirItem.getFileStatus( aFileStatus );
                    if ( rcOSL == osl::FileBase::E_None )
                        AddTempDevFont( pFontCollection, aFileStatus.getFileURL(), "" );
                }
            }
        }

        ImplEnumInfo aInfo;
        aInfo.mhDC          = getHDC();
        aInfo.mpList        = pFontCollection;
        aInfo.mpName        = nullptr;
        aInfo.mbPrinter     = mbPrinter;
        aInfo.mnFontCount   = 0;

        LOGFONTW aLogFont;
        memset( &aLogFont, 0, sizeof( aLogFont ) );
        aLogFont.lfCharSet = DEFAULT_CHARSET;
        aInfo.mpLogFont = &aLogFont;
        EnumFontFamiliesExW( getHDC(), &aLogFont,
            SalEnumFontsProcExW, reinterpret_cast<LPARAM>(&aInfo), 0 );

        // set glyph fallback hook
        static WinGlyphFallbackSubstititution aSubstFallback;
        static WinPreMatchFontSubstititution aPreMatchFont;
        pFontCollection->SetFallbackHook( &aSubstFallback );
        pFontCollection->SetPreMatchHook(&aPreMatchFont);

        SAL_WARN( "vcl.fonts", "WinSalGraphics::GetDevFontList(): leave" );
    }

    void WinSalGraphics::ClearDevFontCache()
    {
        //anything to do here ?
    }

    bool WinSalGraphics::GetGlyphBoundRect(const GlyphItem& rGlyph, tools::Rectangle& rRect)
    {
        auto it = g_BoundRectCache.find(rGlyph.maGlyphId);
        if (it != g_BoundRectCache.end())
        {
            rRect = it->second;
            return true;
        }

        rtl::Reference<WinFontInstance> pFont = mpWinFontEntry[rGlyph.mnFallbackLevel];
        assert(pFont.is());

        HDC hDC = getHDC();
        HFONT hFont = static_cast<HFONT>(GetCurrentObject(hDC, OBJ_FONT));
        float fFontScale = 1.0;
        if (pFont.is())
        {
            if (hFont != pFont->GetHFONT())
                SelectObject(hDC, pFont->GetHFONT());
            fFontScale = pFont->GetScale();
        }

        // use unity matrix
        MAT2 aMat;
        aMat.eM11 = aMat.eM22 = FixedFromDouble( 1.0 );
        aMat.eM12 = aMat.eM21 = FixedFromDouble( 0.0 );

        UINT nGGOFlags = GGO_METRICS;
        nGGOFlags |= GGO_GLYPH_INDEX;

        GLYPHMETRICS aGM;
        aGM.gmptGlyphOrigin.x = aGM.gmptGlyphOrigin.y = 0;
        aGM.gmBlackBoxX = aGM.gmBlackBoxY = 0;
        DWORD nSize = ::GetGlyphOutlineW(hDC, rGlyph.maGlyphId, nGGOFlags, &aGM, 0, nullptr, &aMat);
        if (pFont.is() && hFont != pFont->GetHFONT())
            SelectObject(hDC, hFont);
        if( nSize == GDI_ERROR )
            return false;

        rRect = tools::Rectangle( Point( +aGM.gmptGlyphOrigin.x, -aGM.gmptGlyphOrigin.y ),
            Size( aGM.gmBlackBoxX, aGM.gmBlackBoxY ) );
        rRect.SetLeft(static_cast<int>( fFontScale * rRect.Left() ));
        rRect.SetRight(static_cast<int>( fFontScale * rRect.Right() ) + 1);
        rRect.SetTop(static_cast<int>( fFontScale * rRect.Top() ));
        rRect.SetBottom(static_cast<int>( fFontScale * rRect.Bottom() ) + 1);

        g_BoundRectCache.insert({rGlyph.maGlyphId, rRect});

        return true;
    }

    bool WinSalGraphics::GetGlyphOutline(const GlyphItem& rGlyph,
        basegfx::B2DPolyPolygon& rB2DPolyPoly )
    {
        rB2DPolyPoly.clear();

        HDC  hDC = getHDC();

        // use unity matrix
        MAT2 aMat;
        aMat.eM11 = aMat.eM22 = FixedFromDouble( 1.0 );
        aMat.eM12 = aMat.eM21 = FixedFromDouble( 0.0 );

        UINT nGGOFlags = GGO_NATIVE;
        nGGOFlags |= GGO_GLYPH_INDEX;

        GLYPHMETRICS aGlyphMetrics;
        const DWORD nSize1 = ::GetGlyphOutlineW(hDC, rGlyph.maGlyphId, nGGOFlags, &aGlyphMetrics, 0, nullptr, &aMat);
        if( !nSize1 )       // blank glyphs are ok
            return true;
        else if( nSize1 == GDI_ERROR )
            return false;

        BYTE* pData = new BYTE[ nSize1 ];
        const DWORD nSize2 = ::GetGlyphOutlineW(hDC, rGlyph.maGlyphId, nGGOFlags,
                  &aGlyphMetrics, nSize1, pData, &aMat );

        if( nSize1 != nSize2 )
            return false;

        // TODO: avoid tools polygon by creating B2DPolygon directly
        int     nPtSize = 512;
        Point*  pPoints = new Point[ nPtSize ];
        PolyFlags* pFlags = new PolyFlags[ nPtSize ];

        TTPOLYGONHEADER* pHeader = reinterpret_cast<TTPOLYGONHEADER*>(pData);
        while( reinterpret_cast<BYTE*>(pHeader) < pData+nSize2 )
        {
            // only outline data is interesting
            if( pHeader->dwType != TT_POLYGON_TYPE )
                break;

            // get start point; next start points are end points
            // of previous segment
            sal_uInt16 nPnt = 0;

            long nX = IntTimes256FromFixed( pHeader->pfxStart.x );
            long nY = IntTimes256FromFixed( pHeader->pfxStart.y );
            pPoints[ nPnt ] = Point( nX, nY );
            pFlags[ nPnt++ ] = PolyFlags::Normal;

            bool bHasOfflinePoints = false;
            TTPOLYCURVE* pCurve = reinterpret_cast<TTPOLYCURVE*>( pHeader + 1 );
            pHeader = reinterpret_cast<TTPOLYGONHEADER*>( reinterpret_cast<BYTE*>(pHeader) + pHeader->cb );
            while( reinterpret_cast<BYTE*>(pCurve) < reinterpret_cast<BYTE*>(pHeader) )
            {
                int nNeededSize = nPnt + 16 + 3 * pCurve->cpfx;
                if( nPtSize < nNeededSize )
                {
                    Point* pOldPoints = pPoints;
                    PolyFlags* pOldFlags = pFlags;
                    nPtSize = 2 * nNeededSize;
                    pPoints = new Point[ nPtSize ];
                    pFlags = new PolyFlags[ nPtSize ];
                    for( sal_uInt16 i = 0; i < nPnt; ++i )
                    {
                        pPoints[ i ] = pOldPoints[ i ];
                        pFlags[ i ] = pOldFlags[ i ];
                    }
                    delete[] pOldPoints;
                    delete[] pOldFlags;
                }

                int i = 0;
                if( TT_PRIM_LINE == pCurve->wType )
                {
                    while( i < pCurve->cpfx )
                    {
                        nX = IntTimes256FromFixed( pCurve->apfx[ i ].x );
                        nY = IntTimes256FromFixed( pCurve->apfx[ i ].y );
                        ++i;
                        pPoints[ nPnt ] = Point( nX, nY );
                        pFlags[ nPnt ] = PolyFlags::Normal;
                        ++nPnt;
                    }
                }
                else if( TT_PRIM_QSPLINE == pCurve->wType )
                {
                    bHasOfflinePoints = true;
                    while( i < pCurve->cpfx )
                    {
                        // get control point of quadratic bezier spline
                        nX = IntTimes256FromFixed( pCurve->apfx[ i ].x );
                        nY = IntTimes256FromFixed( pCurve->apfx[ i ].y );
                        ++i;
                        Point aControlP( nX, nY );

                        // calculate first cubic control point
                        // P0 = 1/3 * (PBeg + 2 * PQControl)
                        nX = pPoints[ nPnt-1 ].X() + 2 * aControlP.X();
                        nY = pPoints[ nPnt-1 ].Y() + 2 * aControlP.Y();
                        pPoints[ nPnt+0 ] = Point( (2*nX+3)/6, (2*nY+3)/6 );
                        pFlags[ nPnt+0 ] = PolyFlags::Control;

                        // calculate endpoint of segment
                        nX = IntTimes256FromFixed( pCurve->apfx[ i ].x );
                        nY = IntTimes256FromFixed( pCurve->apfx[ i ].y );

                        if ( i+1 >= pCurve->cpfx )
                        {
                            // endpoint is either last point in segment => advance
                            ++i;
                        }
                        else
                        {
                            // or endpoint is the middle of two control points
                            nX += IntTimes256FromFixed( pCurve->apfx[ i-1 ].x );
                            nY += IntTimes256FromFixed( pCurve->apfx[ i-1 ].y );
                            nX = (nX + 1) / 2;
                            nY = (nY + 1) / 2;
                            // no need to advance, because the current point
                            // is the control point in next bezier spline
                        }

                        pPoints[ nPnt+2 ] = Point( nX, nY );
                        pFlags[ nPnt+2 ] = PolyFlags::Normal;

                        // calculate second cubic control point
                        // P1 = 1/3 * (PEnd + 2 * PQControl)
                        nX = pPoints[ nPnt+2 ].X() + 2 * aControlP.X();
                        nY = pPoints[ nPnt+2 ].Y() + 2 * aControlP.Y();
                        pPoints[ nPnt+1 ] = Point( (2*nX+3)/6, (2*nY+3)/6 );
                        pFlags[ nPnt+1 ] = PolyFlags::Control;

                        nPnt += 3;
                    }
                }

                // next curve segment
                pCurve = reinterpret_cast<TTPOLYCURVE*>(&pCurve->apfx[ i ]);
            }

            // end point is start point for closed contour
            // disabled, because Polygon class closes the contour itself
            // pPoints[nPnt++] = pPoints[0];
            // #i35928#
            // Added again, but add only when not yet closed
            if(pPoints[nPnt - 1] != pPoints[0])
            {
                if( bHasOfflinePoints )
                    pFlags[nPnt] = pFlags[0];

                pPoints[nPnt++] = pPoints[0];
            }

            // convert y-coordinates W32 -> VCL
            for( int i = 0; i < nPnt; ++i )
                pPoints[i].setY(-pPoints[i].Y());

            // insert into polypolygon
            tools::Polygon aPoly( nPnt, pPoints, (bHasOfflinePoints ? pFlags : nullptr) );
            // convert to B2DPolyPolygon
            // TODO: get rid of the intermediate PolyPolygon
            rB2DPolyPoly.append( aPoly.getB2DPolygon() );
        }

        delete[] pPoints;
        delete[] pFlags;

        delete[] pData;

        // rescaling needed for the tools::PolyPolygon conversion
        if( rB2DPolyPoly.count() )
        {
            rtl::Reference<WinFontInstance> pFont = mpWinFontEntry[rGlyph.mnFallbackLevel];
            assert(pFont.is());
            float fFontScale = pFont.is() ? pFont->GetScale() : 1.0;
            const double fFactor(fFontScale/256);
            rB2DPolyPoly.transform(basegfx::utils::createScaleB2DHomMatrix(fFactor, fFactor));
        }

        return true;
    }

    class ScopedFont
    {
    public:
        explicit ScopedFont(WinSalGraphics & rData);

        ~ScopedFont();

    private:
        WinSalGraphics & m_rData;
        HFONT m_hOrigFont;
    };

    ScopedFont::ScopedFont(WinSalGraphics & rData): m_rData(rData), m_hOrigFont(nullptr)
    {
        if (m_rData.mpWinFontEntry[0])
        {
            m_hOrigFont = m_rData.mpWinFontEntry[0]->GetHFONT();
            m_rData.mpWinFontEntry[0]->SetHFONT(nullptr);
        }
    }

    ScopedFont::~ScopedFont()
    {
        if( m_hOrigFont )
        {
            // restore original font, destroy temporary font
            HFONT hTempFont = m_rData.mpWinFontEntry[0]->GetHFONT();
            m_rData.mpWinFontEntry[0]->SetHFONT(m_hOrigFont);
            SelectObject( m_rData.getHDC(), m_hOrigFont );
            DeleteObject( hTempFont );
        }
    }

    class ScopedTrueTypeFont
    {
    public:
        ScopedTrueTypeFont(): m_pFont(nullptr) {}

        ~ScopedTrueTypeFont();

        SFErrCodes open(void const * pBuffer, sal_uInt32 nLen, sal_uInt32 nFaceNum);

        TrueTypeFont * get() const { return m_pFont; }

    private:
        TrueTypeFont * m_pFont;
    };

    ScopedTrueTypeFont::~ScopedTrueTypeFont()
    {
        if (m_pFont != nullptr)
            CloseTTFont(m_pFont);
    }

    SFErrCodes ScopedTrueTypeFont::open(void const * pBuffer, sal_uInt32 nLen,
                                 sal_uInt32 nFaceNum)
    {
        OSL_ENSURE(m_pFont == nullptr, "already open");
        return OpenTTFontBuffer(pBuffer, nLen, nFaceNum, &m_pFont);
    }

    bool WinSalGraphics::CreateFontSubset( const OUString& rToFile,
        const PhysicalFontFace* pFont, const sal_GlyphId* pGlyphIds, const sal_uInt8* pEncoding,
        sal_Int32* pGlyphWidths, int nGlyphCount, FontSubsetInfo& rInfo )
    {
        // TODO: use more of the central font-subsetting code, move stuff there if needed

        // create matching FontSelectPattern
        // we need just enough to get to the font file data
        // use height=1000 for easier debugging (to match psprint's font units)
        FontSelectPattern aIFSD( *pFont, Size(0,1000), 1000.0, 0, false );

        // TODO: much better solution: move SetFont and restoration of old font to caller
        ScopedFont aOldFont(*this);
        float fScale = 1.0;
        HFONT hOldFont = nullptr;
        ImplDoSetFont(aIFSD, pFont, fScale, hOldFont);

        WinFontFace const * pWinFontData = static_cast<WinFontFace const *>(pFont);

    #if OSL_DEBUG_LEVEL > 1
        // get font metrics
        TEXTMETRICW aWinMetric;
        if( !::GetTextMetricsW( getHDC(), &aWinMetric ) )
            return FALSE;

        SAL_WARN_IF( (aWinMetric.tmPitchAndFamily & TMPF_DEVICE), "vcl", "cannot subset device font" );
        SAL_WARN_IF( !(aWinMetric.tmPitchAndFamily & TMPF_TRUETYPE), "vcl", "can only subset TT font" );
    #endif

        OUString aSysPath;
        if( osl_File_E_None != osl_getSystemPathFromFileURL( rToFile.pData, &aSysPath.pData ) )
            return FALSE;
        const rtl_TextEncoding aThreadEncoding = osl_getThreadTextEncoding();
        const OString aToFile(OUStringToOString(aSysPath, aThreadEncoding));

        // check if the font has a CFF-table
        const DWORD nCffTag = CalcTag( "CFF " );
        const RawFontData aRawCffData( getHDC(), nCffTag );
        if( aRawCffData.get() )
        {
            pWinFontData->UpdateFromHDC( getHDC() );
            FontCharMapRef xFontCharMap = pWinFontData->GetFontCharMap();
            xFontCharMap = nullptr;

            // provide a font subset from the CFF-table
            FILE* pOutFile = fopen( aToFile.getStr(), "wb" );
            rInfo.LoadFont( FontType::CFF_FONT, aRawCffData.get(), aRawCffData.size() );
            bool bRC = rInfo.CreateFontSubset( FontType::TYPE1_PFB, pOutFile, nullptr,
                    pGlyphIds, pEncoding, nGlyphCount, pGlyphWidths );
            fclose( pOutFile );
            return bRC;
        }

        // get raw font file data
        const RawFontData xRawFontData( getHDC(), 0 );
        if( !xRawFontData.get() )
            return FALSE;

        // open font file
        sal_uInt32 nFaceNum = 0;
        if( !*xRawFontData.get() )  // TTC candidate
            nFaceNum = ~0U;  // indicate "TTC font extracts only"

        ScopedTrueTypeFont aSftTTF;
        SFErrCodes nRC = aSftTTF.open( xRawFontData.get(), xRawFontData.size(), nFaceNum );
        if( nRC != SFErrCodes::Ok )
            return FALSE;

        TTGlobalFontInfo aTTInfo;
        ::GetTTGlobalFontInfo( aSftTTF.get(), &aTTInfo );
        rInfo.m_nFontType   = FontType::SFNT_TTF;
        rInfo.m_aPSName     = ImplSalGetUniString( aTTInfo.psname );
        rInfo.m_nAscent     = aTTInfo.winAscent;
        rInfo.m_nDescent    = aTTInfo.winDescent;
        rInfo.m_aFontBBox   = tools::Rectangle( Point( aTTInfo.xMin, aTTInfo.yMin ),
                                        Point( aTTInfo.xMax, aTTInfo.yMax ) );
        rInfo.m_nCapHeight  = aTTInfo.yMax; // Well ...

        // subset TTF-glyphs and get their properties
        // take care that subset fonts require the NotDef glyph in pos 0
        int nOrigCount = nGlyphCount;
        sal_uInt16    aShortIDs[ 256 ];
        sal_uInt8 aTempEncs[ 256 ];

        int nNotDef=-1, i;
        for( i = 0; i < nGlyphCount; ++i )
        {
            aTempEncs[i] = pEncoding[i];
            aShortIDs[i] = static_cast<sal_uInt16>(pGlyphIds[i]);
            if (!aShortIDs[i])
                if( nNotDef < 0 )
                    nNotDef = i; // first NotDef glyph found
        }

        if( nNotDef != 0 )
        {
            // add fake NotDef glyph if needed
            if( nNotDef < 0 )
                nNotDef = nGlyphCount++;

            // NotDef glyph must be in pos 0 => swap glyphids
            aShortIDs[ nNotDef ] = aShortIDs[0];
            aTempEncs[ nNotDef ] = aTempEncs[0];
            aShortIDs[0] = 0;
            aTempEncs[0] = 0;
        }
        SAL_WARN_IF( nGlyphCount >= 257, "vcl", "too many glyphs for subsetting" );

        // fill pWidth array
        TTSimpleGlyphMetrics* pMetrics =
            ::GetTTSimpleGlyphMetrics( aSftTTF.get(), aShortIDs, nGlyphCount, aIFSD.mbVertical );
        if( !pMetrics )
            return FALSE;
        sal_uInt16 nNotDefAdv   = pMetrics[0].adv;
        pMetrics[0].adv         = pMetrics[nNotDef].adv;
        pMetrics[nNotDef].adv   = nNotDefAdv;
        for( i = 0; i < nOrigCount; ++i )
            pGlyphWidths[i] = pMetrics[i].adv;
        free( pMetrics );

        // write subset into destination file
        nRC = ::CreateTTFromTTGlyphs( aSftTTF.get(), aToFile.getStr(), aShortIDs,
                aTempEncs, nGlyphCount );
        return (nRC == SFErrCodes::Ok);
    }

    const void* WinSalGraphics::GetEmbedFontData(const PhysicalFontFace* pFont, long* pDataLen)
    {
        // create matching FontSelectPattern
        // we need just enough to get to the font file data
        FontSelectPattern aIFSD( *pFont, Size(0,1000), 1000.0, 0, false );

        ScopedFont aOldFont(*this);

        float fScale = 0.0;
        HFONT hOldFont = nullptr;
        ImplDoSetFont(aIFSD, pFont, fScale, hOldFont);

        // get the raw font file data
        RawFontData aRawFontData( getHDC() );
        *pDataLen = aRawFontData.size();
        if( !aRawFontData.get() )
            return nullptr;

        const unsigned char* pData = aRawFontData.steal();
        return pData;
    }

    void WinSalGraphics::FreeEmbedFontData( const void* pData, long /*nLen*/ )
    {
        delete[] static_cast<char const *>(pData);
    }

    void WinSalGraphics::GetGlyphWidths( const PhysicalFontFace* pFont,
                                         bool bVertical,
                                         std::vector< sal_Int32 >& rWidths,
                                         Ucs2UIntMap& rUnicodeEnc )
    {
        // create matching FontSelectPattern
        // we need just enough to get to the font file data
        FontSelectPattern aIFSD( *pFont, Size(0,1000), 1000.0, 0, false );

        // TODO: much better solution: move SetFont and restoration of old font to caller
        ScopedFont aOldFont(*this);

        float fScale = 0.0;
        HFONT hOldFont = nullptr;
        ImplDoSetFont(aIFSD, pFont, fScale, hOldFont);

        // get raw font file data
        const RawFontData xRawFontData( getHDC() );
        if( !xRawFontData.get() )
            return;

        // open font file
        sal_uInt32 nFaceNum = 0;
        if( !*xRawFontData.get() )  // TTC candidate
            nFaceNum = ~0U;  // indicate "TTC font extracts only"

        ScopedTrueTypeFont aSftTTF;
        SFErrCodes nRC = aSftTTF.open( xRawFontData.get(), xRawFontData.size(), nFaceNum );
        if( nRC != SFErrCodes::Ok )
            return;

        int nGlyphs = GetTTGlyphCount( aSftTTF.get() );
        if( nGlyphs > 0 )
        {
            rWidths.resize(nGlyphs);
            std::vector<sal_uInt16> aGlyphIds(nGlyphs);
            for( int i = 0; i < nGlyphs; i++ )
                aGlyphIds[i] = sal_uInt16(i);
            TTSimpleGlyphMetrics* pMetrics = ::GetTTSimpleGlyphMetrics( aSftTTF.get(),
                                                                        &aGlyphIds[0],
                                                                        nGlyphs,
                                                                        bVertical );
            if( pMetrics )
            {
                for( int i = 0; i< nGlyphs; i++ )
                    rWidths[i] = pMetrics[i].adv;
                free( pMetrics );
                rUnicodeEnc.clear();
            }
            const WinFontFace* pWinFont = static_cast<const WinFontFace*>(pFont);
            FontCharMapRef xFCMap = pWinFont->GetFontCharMap();
            SAL_WARN_IF( !xFCMap.is() || !xFCMap->GetCharCount(), "vcl", "no map" );

            int nCharCount = xFCMap->GetCharCount();
            sal_uInt32 nChar = xFCMap->GetFirstChar();
            for( int i = 0; i < nCharCount; i++ )
            {
                if( nChar < 0x00010000 )
                {
                    sal_uInt16 nGlyph = ::MapChar( aSftTTF.get(),
                                                   static_cast<sal_Ucs>(nChar));
                    if( nGlyph )
                        rUnicodeEnc[ static_cast<sal_Unicode>(nChar) ] = nGlyph;
                }
                nChar = xFCMap->GetNextChar( nChar );
            }

            xFCMap = nullptr;
        }
    }

    /* vim:set shiftwidth=4 softtabstop=4 expandtab: */

↑ V547 Expression 'nNameLen < 32' is always true.

↑ V519 The 'xFontCharMap' variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 1673, 1674.