/* -*- 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.