/* -*- 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 <tools/stream.hxx>
#include <tools/vcompat.hxx>
#include <tools/gen.hxx>
#include <comphelper/configuration.hxx>
#include <unotools/fontcfg.hxx>
#include <unotools/fontdefs.hxx>
#include <o3tl/hash_combine.hxx>
#include <i18nlangtag/mslangid.hxx>

#include <vcl/font.hxx>
#include <vcl/svapp.hxx>
#include <vcl/outdev.hxx>
#include <vcl/virdev.hxx>

#include <impfont.hxx>
#include <fontattributes.hxx>
#include <fontsubset.hxx>
#include <sft.hxx>

#include <algorithm>
#include <string_view>

#include <vcl/TypeSerializer.hxx>

#ifdef _WIN32
#include <vcl/metric.hxx>
#endif

using namespace vcl;

namespace
{
    Font::ImplType& GetGlobalDefault()
    {
        static Font::ImplType gDefault;
        return gDefault;
    }
}

Font::Font() : mpImplFont(GetGlobalDefault())
{
}

Font::Font( const vcl::Font& rFont ) : mpImplFont( rFont.mpImplFont )
{
}

Font::Font( vcl::Font&& rFont ) noexcept : mpImplFont( std::move(rFont.mpImplFont) )
{
}

Font::Font( const OUString& rFamilyName, const Size& rSize )
{
    if (GetFamilyName() != rFamilyName
        || GetAverageFontSize() != rSize)
    {
        auto impl = mpImplFont.get();
        impl->SetFamilyName( rFamilyName );
        impl->SetFontSize( rSize );
    }
}

Font::Font( const OUString& rFamilyName, const OUString& rStyleName, const Size& rSize )
{
    if (GetFamilyName() != rFamilyName
        || GetStyleName() != rStyleName
        || GetAverageFontSize() != rSize)
    {
        auto impl = mpImplFont.get();
        impl->SetFamilyName( rFamilyName );
        impl->SetStyleName( rStyleName );
        impl->SetFontSize( rSize );
    }
}

Font::Font( FontFamily eFamily, const Size& rSize )
{
    if (GetFontFamily() != eFamily
        || GetAverageFontSize() != rSize)
    {
        auto impl = mpImplFont.get();
        impl->SetFamilyType( eFamily );
        impl->SetFontSize( rSize );
    }
}

Font::~Font()
{
}

void Font::SetColor( const Color& rColor )
{
    if (GetColor() != rColor)
    {
        mpImplFont->maColor = rColor;
    }
}

void Font::SetFillColor( const Color& rColor )
{
    if (GetFillColor() != rColor)
    {
        auto impl = mpImplFont.get();
        impl->maFillColor = rColor;
        if ( rColor.IsTransparent() )
            impl->mbTransparent = true;
    }
}

void Font::SetTransparent( bool bTransparent )
{
    if (IsTransparent() != bTransparent)
        mpImplFont->mbTransparent = bTransparent;
}

void Font::SetAlignment( TextAlign eAlign )
{
    if (GetAlignment() != eAlign)
        mpImplFont->SetAlignment(eAlign);
}

void Font::SetFamilyName( const OUString& rFamilyName )
{
    if (GetFamilyName() != rFamilyName)
        mpImplFont->SetFamilyName( rFamilyName );
}

void Font::SetStyleName( const OUString& rStyleName )
{
    if (GetStyleName() != rStyleName)
        mpImplFont->maStyleName = rStyleName;
}

void Font::SetFontSize( const Size& rSize )
{
    if (GetFontSize() != rSize)
        mpImplFont->SetFontSize( rSize );
}

void Font::SetFamily( FontFamily eFamily )
{
    if (std::as_const(mpImplFont)->GetFamilyTypeNoAsk() != eFamily)
        mpImplFont->SetFamilyType( eFamily );
}

void Font::SetCharSet( rtl_TextEncoding eCharSet )
{
    if (GetCharSet() != eCharSet)
        mpImplFont->SetCharSet( eCharSet );
}

void Font::SetLanguageTag( const LanguageTag& rLanguageTag )
{
    if (GetLanguageTag() != rLanguageTag)
        mpImplFont->maLanguageTag = rLanguageTag;
}

void Font::SetCJKContextLanguageTag( const LanguageTag& rLanguageTag )
{
    if (GetCJKContextLanguageTag() != rLanguageTag)
        mpImplFont->maCJKLanguageTag = rLanguageTag;
}

void Font::SetLanguage( LanguageType eLanguage )
{
    if (GetLanguage() != eLanguage)
        mpImplFont->maLanguageTag.reset( eLanguage);
}

void Font::SetCJKContextLanguage( LanguageType eLanguage )
{
    if (GetCJKContextLanguage() != eLanguage)
        mpImplFont->maCJKLanguageTag.reset( eLanguage);
}

void Font::SetPitch( FontPitch ePitch )
{
    if (std::as_const(mpImplFont)->GetPitchNoAsk() != ePitch)
        mpImplFont->SetPitch( ePitch );
}

void Font::SetOrientation( Degree10 nOrientation )
{
    if (GetOrientation() != nOrientation)
        mpImplFont->mnOrientation = nOrientation;
}

void Font::SetVertical( bool bVertical )
{
    if (IsVertical() != bVertical)
        mpImplFont->mbVertical = bVertical;
}

void Font::SetKerning( FontKerning eKerning )
{
    if (GetKerning() != eKerning)
        mpImplFont->meKerning = eKerning;
}

bool Font::IsKerning() const
{
    return mpImplFont->meKerning != FontKerning::NONE;
}

void Font::SetFixKerning( short nSpacing )
{
    if (GetFixKerning() != nSpacing)
        mpImplFont->mnSpacing = nSpacing;
}

short Font::GetFixKerning() const
{
    return mpImplFont->mnSpacing;
}

bool Font::IsFixKerning() const
{
    return mpImplFont->mnSpacing != 0;
}

void Font::SetWeight( FontWeight eWeight )
{
    if (std::as_const(mpImplFont)->GetWeightNoAsk() != eWeight)
        mpImplFont->SetWeight( eWeight );
}

void Font::SetWidthType( FontWidth eWidth )
{
    if (std::as_const(mpImplFont)->GetWidthTypeNoAsk() != eWidth)
        mpImplFont->SetWidthType( eWidth );
}

void Font::SetItalic( FontItalic eItalic )
{
    if (std::as_const(mpImplFont)->GetItalicNoAsk() != eItalic)
        mpImplFont->SetItalic( eItalic );
}

void Font::SetOutline( bool bOutline )
{
    if (IsOutline() != bOutline)
        mpImplFont->mbOutline = bOutline;
}

void Font::SetShadow( bool bShadow )
{
    if (IsShadow() != bShadow)
        mpImplFont->mbShadow = bShadow;
}

void Font::SetUnderline( FontLineStyle eUnderline )
{
    if (GetUnderline() != eUnderline)
        mpImplFont->meUnderline = eUnderline;
}

void Font::SetOverline( FontLineStyle eOverline )
{
    if (GetOverline() != eOverline)
        mpImplFont->meOverline = eOverline;
}

void Font::SetStrikeout( FontStrikeout eStrikeout )
{
    if (GetStrikeout() != eStrikeout)
        mpImplFont->meStrikeout = eStrikeout;
}

void Font::SetRelief( FontRelief eRelief )
{
    if (GetRelief() != eRelief)
        mpImplFont->meRelief = eRelief;
}

void Font::SetEmphasisMark( FontEmphasisMark eEmphasisMark )
{
    if (GetEmphasisMark() != eEmphasisMark)
        mpImplFont->meEmphasisMark = eEmphasisMark;
}

void Font::SetWordLineMode( bool bWordLine )
{
    if (IsWordLineMode() != bWordLine)
        mpImplFont->mbWordLine = bWordLine;
}

Font& Font::operator=( const vcl::Font& rFont )
{
    mpImplFont = rFont.mpImplFont;
    return *this;
}

Font& Font::operator=( vcl::Font&& rFont ) noexcept
{
    mpImplFont = std::move(rFont.mpImplFont);
    return *this;
}

FontEmphasisMark Font::GetEmphasisMarkStyle() const
{
    FontEmphasisMark nEmphasisMark = GetEmphasisMark();

    // If no Position is set, then calculate the default position, which
    // depends on the language
    if (!(nEmphasisMark & (FontEmphasisMark::PosAbove | FontEmphasisMark::PosBelow)))
    {
        LanguageType eLang = GetLanguage();
        // In Chinese Simplified the EmphasisMarks are below/left
        if (MsLangId::isSimplifiedChinese(eLang))
        {
            nEmphasisMark |= FontEmphasisMark::PosBelow;
        }
        else
        {
            eLang = GetCJKContextLanguage();

            // In Chinese Simplified the EmphasisMarks are below/left
            if (MsLangId::isSimplifiedChinese(eLang))
                nEmphasisMark |= FontEmphasisMark::PosBelow;
            else
                nEmphasisMark |= FontEmphasisMark::PosAbove;
        }
    }

    return nEmphasisMark;
}

bool Font::operator==( const vcl::Font& rFont ) const
{
    return mpImplFont == rFont.mpImplFont;
}

bool Font::EqualIgnoreColor( const vcl::Font& rFont ) const
{
    return mpImplFont->EqualIgnoreColor( *rFont.mpImplFont );
}

size_t Font::GetHashValueIgnoreColor() const
{
    return mpImplFont->GetHashValueIgnoreColor();
}

void Font::Merge( const vcl::Font& rFont )
{
    if ( !rFont.GetFamilyName().isEmpty() )
    {
        SetFamilyName( rFont.GetFamilyName() );
        SetStyleName( rFont.GetStyleName() );
        SetCharSet( rFont.GetCharSet() );
        SetLanguageTag( rFont.GetLanguageTag() );
        SetCJKContextLanguageTag( rFont.GetCJKContextLanguageTag() );
        SetFamily( rFont.GetFamilyType() );
        SetPitch( rFont.GetPitch() );
    }

    if ( rFont.GetWeight() != WEIGHT_DONTKNOW )
        SetWeight( rFont.GetWeight() );
    if ( rFont.GetItalic() != ITALIC_DONTKNOW )
        SetItalic( rFont.GetItalic() );
    if ( rFont.GetWidthType() != WIDTH_DONTKNOW )
        SetWidthType( rFont.GetWidthType() );

    if ( rFont.GetFontSize().Height() )
        SetFontSize( rFont.GetFontSize() );
    if ( rFont.GetUnderline() != LINESTYLE_DONTKNOW )
    {
        SetUnderline( rFont.GetUnderline() );
        SetWordLineMode( rFont.IsWordLineMode() );
    }
    if ( rFont.GetOverline() != LINESTYLE_DONTKNOW )
    {
        SetOverline( rFont.GetOverline() );
        SetWordLineMode( rFont.IsWordLineMode() );
    }
    if ( rFont.GetStrikeout() != STRIKEOUT_DONTKNOW )
    {
        SetStrikeout( rFont.GetStrikeout() );
        SetWordLineMode( rFont.IsWordLineMode() );
    }

    // Defaults?
    SetOrientation( rFont.GetOrientation() );
    SetVertical( rFont.IsVertical() );
    SetEmphasisMark( rFont.GetEmphasisMark() );
    SetKerning( rFont.IsKerning() ? FontKerning::FontSpecific : FontKerning::NONE );
    SetOutline( rFont.IsOutline() );
    SetShadow( rFont.IsShadow() );
    SetRelief( rFont.GetRelief() );
}

void Font::GetFontAttributes( FontAttributes& rAttrs ) const
{
    rAttrs.SetFamilyName( GetFamilyName() );
    rAttrs.SetStyleName( GetStyleName() );
    rAttrs.SetFamilyType( GetFamilyType() );
    rAttrs.SetPitch( GetPitch() );
    rAttrs.SetItalic( GetItalic() );
    rAttrs.SetWeight( GetWeight() );
    rAttrs.SetWidthType( WIDTH_DONTKNOW );
    rAttrs.SetMicrosoftSymbolEncoded( GetCharSet() == RTL_TEXTENCODING_SYMBOL );
}

// tdf#127471 for corrections on EMF/WMF we need the AvgFontWidth in Windows-specific notation
tools::Long Font::GetOrCalculateAverageFontWidth() const
{
    if(0 == mpImplFont->GetCalculatedAverageFontWidth())
    {
        // VirtualDevice is not thread safe
        SolarMutexGuard aGuard;

        // create unscaled copy of font (this), a VirtualDevice and set it there
        vcl::Font aUnscaledFont(*this);
        ScopedVclPtr<VirtualDevice> pTempVirtualDevice(VclPtr<VirtualDevice>::Create());
        aUnscaledFont.SetAverageFontWidth(0);
        pTempVirtualDevice->SetFont(aUnscaledFont);

#ifdef _WIN32
        // on Windows systems use FontMetric to get/create AverageFontWidth from system
        const FontMetric aMetric(pTempVirtualDevice->GetFontMetric());
        const_cast<Font*>(this)->mpImplFont->SetCalculatedAverageFontWidth(aMetric.GetAverageFontWidth());
#else
        // On non-Windows systems we need to calculate AvgFontWidth
        // as close as possible (discussion see documentation in task),
        // so calculate it. For discussion of method used, see task
        // buffer measure string creation, will always use the same
        static constexpr OUString aMeasureString
            = u"\u0020\u0021\u0022\u0023\u0024\u0025\u0026\u0027"
               "\u0028\u0029\u002A\u002B\u002C\u002D\u002E\u002F"
               "\u0030\u0031\u0032\u0033\u0034\u0035\u0036\u0037"
               "\u0038\u0039\u003A\u003B\u003C\u003D\u003E\u003F"
               "\u0040\u0041\u0042\u0043\u0044\u0045\u0046\u0047"
               "\u0048\u0049\u004A\u004B\u004C\u004D\u004E\u004F"
               "\u0050\u0051\u0052\u0053\u0054\u0055\u0056\u0057"
               "\u0058\u0059\u005A\u005B\u005C\u005D\u005E\u005F"
               "\u0060\u0061\u0062\u0063\u0064\u0065\u0066\u0067"
               "\u0068\u0069\u006A\u006B\u006C\u006D\u006E\u006F"
               "\u0070\u0071\u0072\u0073\u0074\u0075\u0076\u0077"
               "\u0078\u0079\u007A\u007B\u007C\u007D\u007E"_ustr;

        const double fAverageFontWidth(
            pTempVirtualDevice->GetTextWidth(aMeasureString) /
            static_cast<double>(aMeasureString.getLength()));
        const_cast<Font*>(this)->mpImplFont->SetCalculatedAverageFontWidth(basegfx::fround(fAverageFontWidth));
#endif
    }

    return mpImplFont->GetCalculatedAverageFontWidth();
}

SvStream& ReadImplFont( SvStream& rIStm, ImplFont& rImplFont, tools::Long& rnNormedFontScaling )
{
    VersionCompatRead aCompat( rIStm );
    sal_uInt16      nTmp16(0);
    sal_Int16       nTmps16(0);
    bool            bTmp(false);
    sal_uInt8       nTmp8(0);

    rImplFont.SetFamilyName( rIStm.ReadUniOrByteString(rIStm.GetStreamCharSet()) );
    rImplFont.maStyleName = rIStm.ReadUniOrByteString(rIStm.GetStreamCharSet());
    TypeSerializer aSerializer(rIStm);
    aSerializer.readSize(rImplFont.maAverageFontSize);

    static const bool bFuzzing = comphelper::IsFuzzing();
    if (bFuzzing)
    {
        if (rImplFont.maAverageFontSize.Width() > 8192)
        {
            SAL_WARN("vcl.gdi", "suspicious average width of: " << rImplFont.maAverageFontSize.Width());
            rImplFont.maAverageFontSize.setWidth(8192);
        }
        if (rImplFont.maAverageFontSize.Height() > 8192)
        {
            SAL_WARN("vcl.gdi", "suspicious average height of: " << rImplFont.maAverageFontSize.Height());
            rImplFont.maAverageFontSize.setHeight(8192);
        }
    }

    rIStm.ReadUInt16( nTmp16 ); rImplFont.SetCharSet( static_cast<rtl_TextEncoding>(nTmp16) );
    rIStm.ReadUInt16( nTmp16 ); rImplFont.SetFamilyType( static_cast<FontFamily>(nTmp16) );
    rIStm.ReadUInt16( nTmp16 ); rImplFont.SetPitch( static_cast<FontPitch>(nTmp16) );
    rIStm.ReadUInt16( nTmp16 ); rImplFont.SetWeight( static_cast<FontWeight>(nTmp16) );
    rIStm.ReadUInt16( nTmp16 ); rImplFont.meUnderline = static_cast<FontLineStyle>(nTmp16);
    rIStm.ReadUInt16( nTmp16 ); rImplFont.meStrikeout = static_cast<FontStrikeout>(nTmp16);
    rIStm.ReadUInt16( nTmp16 ); rImplFont.SetItalic( static_cast<FontItalic>(nTmp16) );
    rIStm.ReadUInt16( nTmp16 ); rImplFont.maLanguageTag.reset( LanguageType(nTmp16) );
    rIStm.ReadUInt16( nTmp16 ); rImplFont.meWidthType = static_cast<FontWidth>(nTmp16);

    rIStm.ReadInt16( nTmps16 ); rImplFont.mnOrientation = Degree10(nTmps16);

    rIStm.ReadCharAsBool( bTmp ); rImplFont.mbWordLine = bTmp;
    rIStm.ReadCharAsBool( bTmp ); rImplFont.mbOutline = bTmp;
    rIStm.ReadCharAsBool( bTmp ); rImplFont.mbShadow = bTmp;
    rIStm.ReadUChar( nTmp8 ); rImplFont.meKerning = static_cast<FontKerning>(nTmp8);

    if( aCompat.GetVersion() >= 2 )
    {
        rIStm.ReadUChar( nTmp8 );     rImplFont.meRelief = static_cast<FontRelief>(nTmp8);
        rIStm.ReadUInt16( nTmp16 );   rImplFont.maCJKLanguageTag.reset( LanguageType(nTmp16) );
        rIStm.ReadCharAsBool( bTmp ); rImplFont.mbVertical = bTmp;
        rIStm.ReadUInt16( nTmp16 );
        rImplFont.meEmphasisMark = static_cast<FontEmphasisMark>(nTmp16 & o3tl::typed_flags<FontEmphasisMark>::mask);
    }

    if( aCompat.GetVersion() >= 3 )
    {
        rIStm.ReadUInt16( nTmp16 ); rImplFont.meOverline = static_cast<FontLineStyle>(nTmp16);
    }

    // tdf#127471 read NormedFontScaling
    if( aCompat.GetVersion() >= 4 )
    {
        sal_Int32 nNormedFontScaling(0);
        rIStm.ReadInt32(nNormedFontScaling);
        rnNormedFontScaling = nNormedFontScaling;
    }

    if( aCompat.GetVersion() >= 5 )
    {
        rIStm.ReadInt16( nTmps16 );
        rImplFont.mnSpacing = nTmps16;
    }

    // Relief
    // CJKContextLanguage

    return rIStm;
}

SvStream& WriteImplFont( SvStream& rOStm, const ImplFont& rImplFont, tools::Long nNormedFontScaling )
{
    // tdf#127471 increase to version 4
    // tdf#66819 increase to version 5
    VersionCompatWrite aCompat( rOStm, 5 );

    TypeSerializer aSerializer(rOStm);
    rOStm.WriteUniOrByteString( rImplFont.GetFamilyName(), rOStm.GetStreamCharSet() );
    rOStm.WriteUniOrByteString( rImplFont.GetStyleName(), rOStm.GetStreamCharSet() );
    aSerializer.writeSize(rImplFont.maAverageFontSize);

    rOStm.WriteUInt16( GetStoreCharSet( rImplFont.GetCharSet() ) );
    rOStm.WriteUInt16( rImplFont.GetFamilyTypeNoAsk() );
    rOStm.WriteUInt16( rImplFont.GetPitchNoAsk() );
    rOStm.WriteUInt16( rImplFont.GetWeightNoAsk() );
    rOStm.WriteUInt16( rImplFont.meUnderline );
    rOStm.WriteUInt16( rImplFont.meStrikeout );
    rOStm.WriteUInt16( rImplFont.GetItalicNoAsk() );
    rOStm.WriteUInt16( static_cast<sal_uInt16>(rImplFont.maLanguageTag.getLanguageType( false)) );
    rOStm.WriteUInt16( rImplFont.GetWidthTypeNoAsk() );

    rOStm.WriteInt16( rImplFont.mnOrientation.get() );

    rOStm.WriteBool( rImplFont.mbWordLine );
    rOStm.WriteBool( rImplFont.mbOutline );
    rOStm.WriteBool( rImplFont.mbShadow );
    rOStm.WriteUChar( static_cast<sal_uInt8>(rImplFont.meKerning) );

    // new in version 2
    rOStm.WriteUChar( static_cast<unsigned char>(rImplFont.meRelief) );
    rOStm.WriteUInt16( static_cast<sal_uInt16>(rImplFont.maCJKLanguageTag.getLanguageType( false)) );
    rOStm.WriteBool( rImplFont.mbVertical );
    rOStm.WriteUInt16( static_cast<sal_uInt16>(rImplFont.meEmphasisMark) );

    // new in version 3
    rOStm.WriteUInt16( rImplFont.meOverline );

    // new in version 4, NormedFontScaling
    rOStm.WriteInt32(nNormedFontScaling);

    // new in version 5
    rOStm.WriteInt16( rImplFont.mnSpacing );
    return rOStm;
}

SvStream& ReadFont( SvStream& rIStm, vcl::Font& rFont )
{
    // tdf#127471 try to read NormedFontScaling
    tools::Long nNormedFontScaling(0);
    SvStream& rRetval(ReadImplFont( rIStm, *rFont.mpImplFont, nNormedFontScaling ));

    if (nNormedFontScaling > 0)
    {
#ifdef _WIN32
        // we run on windows and a NormedFontScaling was written
        if(rFont.GetFontSize().getWidth() == nNormedFontScaling)
        {
            // the writing producer was running on a non-windows system, adapt to needed windows
            // system-specific pre-multiply
            const tools::Long nHeight(std::max<tools::Long>(rFont.GetFontSize().getHeight(), 0));
            sal_uInt32 nScaledWidth(0);

            if(nHeight > 0)
            {
                vcl::Font aUnscaledFont(rFont);
                aUnscaledFont.SetAverageFontWidth(0);
                const FontMetric aUnscaledFontMetric(Application::GetDefaultDevice()->GetFontMetric(aUnscaledFont));

                if (nHeight > 0)
                {
                    const double fScaleFactor(static_cast<double>(nNormedFontScaling) / static_cast<double>(nHeight));
                    nScaledWidth = basegfx::fround(static_cast<double>(aUnscaledFontMetric.GetAverageFontWidth()) * fScaleFactor);
                }
            }

            rFont.SetAverageFontWidth(nScaledWidth);
        }
        else
        {
            // the writing producer was on a windows system, correct pre-multiplied value
            // is already set, nothing to do. Ignore 2nd value. Here a check
            // could be done if adapting the 2nd, NormedFontScaling value would be similar to
            // the set value for plausibility reasons
        }
#else
        // we do not run on windows and a NormedFontScaling was written
        if(rFont.GetFontSize().getWidth() == nNormedFontScaling)
        {
            // the writing producer was not on a windows system, correct value
            // already set, nothing to do
        }
        else
        {
            // the writing producer was on a windows system, correct FontScaling.
            // The correct non-pre-multiplied value is the 2nd one, use it
            rFont.SetAverageFontWidth(nNormedFontScaling);
        }
#endif
    }

    return rRetval;
}

SvStream& WriteFont( SvStream& rOStm, const vcl::Font& rFont )
{
    // tdf#127471 prepare NormedFontScaling for additional export
    tools::Long nNormedFontScaling(rFont.GetFontSize().getWidth());

    // FontScaling usage at vcl-Font is detected by checking that FontWidth != 0
    if (nNormedFontScaling > 0)
    {
        const tools::Long nHeight(std::max<tools::Long>(rFont.GetFontSize().getHeight(), 0));

        // check for negative height
        if(0 == nHeight)
        {
            nNormedFontScaling = 0;
        }
        else
        {
#ifdef _WIN32
            // for WIN32 the value is pre-multiplied with AverageFontWidth
            // which makes it system-dependent. Turn that back to have the
            // normed non-windows form of it for export as 2nd value
            vcl::Font aUnscaledFont(rFont);
            aUnscaledFont.SetAverageFontWidth(0);
            const FontMetric aUnscaledFontMetric(
                Application::GetDefaultDevice()->GetFontMetric(aUnscaledFont));

            if (aUnscaledFontMetric.GetAverageFontWidth() > 0)
            {
                const double fScaleFactor(
                    static_cast<double>(nNormedFontScaling)
                    / static_cast<double>(aUnscaledFontMetric.GetAverageFontWidth()));
                nNormedFontScaling = static_cast<tools::Long>(fScaleFactor * nHeight);
            }
#endif
        }
    }

    return WriteImplFont( rOStm, *rFont.mpImplFont, nNormedFontScaling );
}

namespace
{
    bool identifyTrueTypeFont( const void* i_pBuffer, sal_uInt32 i_nSize, Font& o_rResult )
    {
        bool bResult = false;
        TrueTypeFont* pTTF = nullptr;
        if( OpenTTFontBuffer( i_pBuffer, i_nSize, 0, &pTTF ) == SFErrCodes::Ok )
        {
            TTGlobalFontInfo aInfo;
            GetTTGlobalFontInfo( pTTF, &aInfo );
            // most importantly: the family name
            if( !aInfo.ufamily.isEmpty() )
                o_rResult.SetFamilyName( aInfo.ufamily );
            else if( !aInfo.family.isEmpty() )
                o_rResult.SetFamilyName( OStringToOUString( aInfo.family, RTL_TEXTENCODING_ASCII_US ) );
            // set weight
            if( aInfo.weight )
            {
                if( aInfo.weight < FW_EXTRALIGHT )
                    o_rResult.SetWeight( WEIGHT_THIN );
                else if( aInfo.weight < FW_LIGHT )
                    o_rResult.SetWeight( WEIGHT_ULTRALIGHT );
                else if( aInfo.weight < FW_NORMAL )
                    o_rResult.SetWeight( WEIGHT_LIGHT );
                else if( aInfo.weight < FW_MEDIUM )
                    o_rResult.SetWeight( WEIGHT_NORMAL );
                else if( aInfo.weight < FW_SEMIBOLD )
                    o_rResult.SetWeight( WEIGHT_MEDIUM );
                else if( aInfo.weight < FW_BOLD )
                    o_rResult.SetWeight( WEIGHT_SEMIBOLD );
                else if( aInfo.weight < FW_EXTRABOLD )
                    o_rResult.SetWeight( WEIGHT_BOLD );
                else if( aInfo.weight < FW_BLACK )
                    o_rResult.SetWeight( WEIGHT_ULTRABOLD );
                else
                    o_rResult.SetWeight( WEIGHT_BLACK );
            }
            else
                o_rResult.SetWeight( (aInfo.macStyle & 1) ? WEIGHT_BOLD : WEIGHT_NORMAL );
            // set width
            if( aInfo.width )
            {
                if( aInfo.width == FWIDTH_ULTRA_CONDENSED )
                    o_rResult.SetAverageFontWidth( WIDTH_ULTRA_CONDENSED );
                else if( aInfo.width == FWIDTH_EXTRA_CONDENSED )
                    o_rResult.SetAverageFontWidth( WIDTH_EXTRA_CONDENSED );
                else if( aInfo.width == FWIDTH_CONDENSED )
                    o_rResult.SetAverageFontWidth( WIDTH_CONDENSED );
                else if( aInfo.width == FWIDTH_SEMI_CONDENSED )
                    o_rResult.SetAverageFontWidth( WIDTH_SEMI_CONDENSED );
                else if( aInfo.width == FWIDTH_NORMAL )
                    o_rResult.SetAverageFontWidth( WIDTH_NORMAL );
                else if( aInfo.width == FWIDTH_SEMI_EXPANDED )
                    o_rResult.SetAverageFontWidth( WIDTH_SEMI_EXPANDED );
                else if( aInfo.width == FWIDTH_EXPANDED )
                    o_rResult.SetAverageFontWidth( WIDTH_EXPANDED );
                else if( aInfo.width == FWIDTH_EXTRA_EXPANDED )
                    o_rResult.SetAverageFontWidth( WIDTH_EXTRA_EXPANDED );
                else if( aInfo.width >= FWIDTH_ULTRA_EXPANDED )
                    o_rResult.SetAverageFontWidth( WIDTH_ULTRA_EXPANDED );
            }
            // set italic
            o_rResult.SetItalic( (aInfo.italicAngle != 0) ? ITALIC_NORMAL : ITALIC_NONE );

            // set pitch
            o_rResult.SetPitch( (aInfo.pitch == 0) ? PITCH_VARIABLE : PITCH_FIXED );

            // set style name
            if( !aInfo.usubfamily.isEmpty() )
                o_rResult.SetStyleName( aInfo.usubfamily );
            else if( !aInfo.subfamily.isEmpty() )
                o_rResult.SetStyleName( OUString::createFromAscii( aInfo.subfamily ) );

            // cleanup
            CloseTTFont( pTTF );
            // success
            bResult = true;
        }
        return bResult;
    }

    struct WeightSearchEntry
    {
        const char* string;
        int         string_len;
        FontWeight  weight;

        bool operator<( const WeightSearchEntry& rRight ) const
        {
            return rtl_str_compareIgnoreAsciiCase_WithLength( string, string_len, rRight.string, rRight.string_len ) < 0;
        }
    }
    const weight_table[] =
    {
        { "black", 5, WEIGHT_BLACK },
        { "bold", 4, WEIGHT_BOLD },
        { "book", 4, WEIGHT_LIGHT },
        { "demi", 4, WEIGHT_SEMIBOLD },
        { "heavy", 5, WEIGHT_BLACK },
        { "light", 5, WEIGHT_LIGHT },
        { "medium", 6, WEIGHT_MEDIUM },
        { "regular", 7, WEIGHT_NORMAL },
        { "super", 5, WEIGHT_ULTRABOLD },
        { "thin", 4, WEIGHT_THIN }
    };

    bool identifyType1Font( const char* i_pBuffer, sal_uInt32 i_nSize, Font& o_rResult )
    {
        // might be a type1, find eexec
        const char* pStream = i_pBuffer;
        const char* const pExec = "eexec";
        const char* pExecPos = std::search( pStream, pStream+i_nSize, pExec, pExec+5 );
        if( pExecPos != pStream+i_nSize)
        {
            // find /FamilyName entry
            static const char* const pFam = "/FamilyName";
            const char* pFamPos = std::search( pStream, pExecPos, pFam, pFam+11 );
            if( pFamPos != pExecPos )
            {
                // extract the string value behind /FamilyName
                const char* pOpen = pFamPos+11;
                while( pOpen < pExecPos && *pOpen != '(' )
                    pOpen++;
                const char* pClose = pOpen;
                while( pClose < pExecPos && *pClose != ')' )
                    pClose++;
                if( pClose - pOpen > 1 )
                {
                    o_rResult.SetFamilyName( OStringToOUString( std::string_view( pOpen+1, pClose-pOpen-1 ), RTL_TEXTENCODING_ASCII_US ) );
                }
            }

            // parse /ItalicAngle
            static const char* const pItalic = "/ItalicAngle";
            const char* pItalicPos = std::search( pStream, pExecPos, pItalic, pItalic+12 );
            if( pItalicPos != pExecPos )
            {
                const char* pItalicEnd = pItalicPos + 12;
                auto nItalic = rtl_str_toInt64_WithLength(pItalicEnd, 10, pExecPos - pItalicEnd);
                o_rResult.SetItalic( (nItalic != 0) ? ITALIC_NORMAL : ITALIC_NONE );
            }

            // parse /Weight
            static const char* const pWeight = "/Weight";
            const char* pWeightPos = std::search( pStream, pExecPos, pWeight, pWeight+7 );
            if( pWeightPos != pExecPos )
            {
                // extract the string value behind /Weight
                const char* pOpen = pWeightPos+7;
                while( pOpen < pExecPos && *pOpen != '(' )
                    pOpen++;
                const char* pClose = pOpen;
                while( pClose < pExecPos && *pClose != ')' )
                    pClose++;
                if( pClose - pOpen > 1 )
                {
                    WeightSearchEntry aEnt;
                    aEnt.string = pOpen+1;
                    aEnt.string_len = (pClose-pOpen)-1;
                    aEnt.weight = WEIGHT_NORMAL;
                    WeightSearchEntry const * pFound = std::lower_bound( std::begin(weight_table), std::end(weight_table), aEnt );
                    if( pFound != std::end(weight_table) &&
                        rtl_str_compareIgnoreAsciiCase_WithLength( pFound->string, pFound->string_len, aEnt.string, aEnt.string_len) == 0 )
                        o_rResult.SetWeight( pFound->weight );
                }
            }

            // parse isFixedPitch
            static const char* const pFixed = "/isFixedPitch";
            const char* pFixedPos = std::search( pStream, pExecPos, pFixed, pFixed+13 );
            if( pFixedPos != pExecPos )
            {
                // skip whitespace
                while( pFixedPos < pExecPos-4 &&
                       ( *pFixedPos == ' '  ||
                         *pFixedPos == '\t' ||
                         *pFixedPos == '\r' ||
                         *pFixedPos == '\n' ) )
                {
                    pFixedPos++;
                }
                // find "true" value
                if( rtl_str_compareIgnoreAsciiCase_WithLength( pFixedPos, 4, "true", 4 ) == 0 )
                    o_rResult.SetPitch( PITCH_FIXED );
                else
                    o_rResult.SetPitch( PITCH_VARIABLE );
            }
        }
        return false;
    }
}

Font Font::identifyFont( const void* i_pBuffer, sal_uInt32 i_nSize )
{
    Font aResult;
    if( ! identifyTrueTypeFont( i_pBuffer, i_nSize, aResult ) )
    {
        const char* pStream = static_cast<const char*>(i_pBuffer);
        if( pStream && i_nSize > 100 &&
             *pStream == '%' && pStream[1] == '!' )
        {
            identifyType1Font( pStream, i_nSize, aResult );
        }
    }

    return aResult;
}

// The inlines from the font.hxx header are now instantiated for pImpl-ification
const Color& Font::GetColor() const { return mpImplFont->maColor; }
const Color& Font::GetFillColor() const { return mpImplFont->maFillColor; }
bool Font::IsTransparent() const { return mpImplFont->mbTransparent; }

TextAlign Font::GetAlignment() const { return mpImplFont->GetAlignment(); }

const OUString& Font::GetFamilyName() const { return mpImplFont->GetFamilyName(); }
const OUString& Font::GetStyleName() const { return mpImplFont->maStyleName; }
const FontFamily& Font::GetFontFamily() const { return mpImplFont->meFamily; }

const Size& Font::GetFontSize() const { return mpImplFont->GetFontSize(); }
void Font::SetFontHeight( tools::Long nHeight ) { SetFontSize( Size( std::as_const(mpImplFont)->GetFontSize().Width(), nHeight ) ); }
tools::Long Font::GetFontHeight() const { return mpImplFont->GetFontSize().Height(); }
void Font::SetAverageFontWidth( tools::Long nWidth ) { SetFontSize( Size( nWidth, std::as_const(mpImplFont)->GetFontSize().Height() ) ); }
tools::Long Font::GetAverageFontWidth() const { return mpImplFont->GetFontSize().Width(); }
const Size& Font::GetAverageFontSize() const { return mpImplFont->maAverageFontSize; }

rtl_TextEncoding Font::GetCharSet() const { return mpImplFont->GetCharSet(); }

const LanguageTag& Font::GetLanguageTag() const { return mpImplFont->maLanguageTag; }
const LanguageTag& Font::GetCJKContextLanguageTag() const { return mpImplFont->maCJKLanguageTag; }
LanguageType Font::GetLanguage() const { return mpImplFont->maLanguageTag.getLanguageType( false); }
LanguageType Font::GetCJKContextLanguage() const { return mpImplFont->maCJKLanguageTag.getLanguageType( false); }

Degree10 Font::GetOrientation() const { return mpImplFont->mnOrientation; }
bool Font::IsVertical() const { return mpImplFont->mbVertical; }
FontKerning Font::GetKerning() const { return mpImplFont->meKerning; }

FontPitch Font::GetPitchMaybeAskConfig() { return mpImplFont->GetPitch(); }
FontWeight Font::GetWeightMaybeAskConfig() { return mpImplFont->GetWeight(); }
FontWidth Font::GetWidthTypeMaybeAskConfig() { return mpImplFont->GetWidthType(); }
FontItalic Font::GetItalicMaybeAskConfig() { return mpImplFont->GetItalic(); }
FontFamily Font::GetFamilyTypeMaybeAskConfig() { return mpImplFont->GetFamilyType(); }

FontPitch Font::GetPitch() const { return mpImplFont->GetPitchNoAsk(); }
FontWeight Font::GetWeight() const { return mpImplFont->GetWeightNoAsk(); }
FontWidth Font::GetWidthType() const { return mpImplFont->GetWidthTypeNoAsk(); }
FontItalic Font::GetItalic() const { return mpImplFont->GetItalicNoAsk(); }
FontFamily Font::GetFamilyType() const { return mpImplFont->GetFamilyTypeNoAsk(); }

int Font::GetQuality() const { return mpImplFont->GetQuality(); }
void Font::SetQuality( int nQuality ) { mpImplFont->SetQuality( nQuality ); }
void Font::IncreaseQualityBy( int nQualityAmount ) { mpImplFont->IncreaseQualityBy( nQualityAmount ); }
void Font::DecreaseQualityBy( int nQualityAmount ) { mpImplFont->DecreaseQualityBy( nQualityAmount ); }

bool Font::IsOutline() const { return mpImplFont->mbOutline; }
bool Font::IsShadow() const { return mpImplFont->mbShadow; }
FontRelief Font::GetRelief() const { return mpImplFont->meRelief; }
FontLineStyle Font::GetUnderline() const { return mpImplFont->meUnderline; }
FontLineStyle Font::GetOverline()  const { return mpImplFont->meOverline; }
FontStrikeout Font::GetStrikeout() const { return mpImplFont->meStrikeout; }
FontEmphasisMark Font::GetEmphasisMark() const { return mpImplFont->meEmphasisMark; }
bool Font::IsWordLineMode() const { return mpImplFont->mbWordLine; }
bool Font::IsSameInstance( const vcl::Font& rFont ) const { return (mpImplFont == rFont.mpImplFont); }


ImplFont::ImplFont() :
    meWeight( WEIGHT_DONTKNOW ),
    meFamily( FAMILY_DONTKNOW ),
    mePitch( PITCH_DONTKNOW ),
    meWidthType( WIDTH_DONTKNOW ),
    meItalic( ITALIC_NONE ),
    meAlign( ALIGN_TOP ),
    meUnderline( LINESTYLE_NONE ),
    meOverline( LINESTYLE_NONE ),
    meStrikeout( STRIKEOUT_NONE ),
    meRelief( FontRelief::NONE ),
    meEmphasisMark( FontEmphasisMark::NONE ),
    meKerning( FontKerning::FontSpecific ),
    mnSpacing( 0 ),
    meCharSet( RTL_TEXTENCODING_DONTKNOW ),
    maLanguageTag( LANGUAGE_DONTKNOW ),
    maCJKLanguageTag( LANGUAGE_DONTKNOW ),
    mbOutline( false ),
    mbConfigLookup( false ),
    mbShadow( false ),
    mbVertical( false ),
    mbTransparent( true ),
    maColor( COL_TRANSPARENT ),
    maFillColor( COL_TRANSPARENT ),
    mbWordLine( false ),
    mnOrientation( 0 ),
    mnQuality( 0 ),
    mnCalculatedAverageFontWidth( 0 )
{}

ImplFont::ImplFont( const ImplFont& rImplFont ) :
    maFamilyName( rImplFont.maFamilyName ),
    maStyleName( rImplFont.maStyleName ),
    meWeight( rImplFont.meWeight ),
    meFamily( rImplFont.meFamily ),
    mePitch( rImplFont.mePitch ),
    meWidthType( rImplFont.meWidthType ),
    meItalic( rImplFont.meItalic ),
    meAlign( rImplFont.meAlign ),
    meUnderline( rImplFont.meUnderline ),
    meOverline( rImplFont.meOverline ),
    meStrikeout( rImplFont.meStrikeout ),
    meRelief( rImplFont.meRelief ),
    meEmphasisMark( rImplFont.meEmphasisMark ),
    meKerning( rImplFont.meKerning ),
    mnSpacing( rImplFont.mnSpacing ),
    maAverageFontSize( rImplFont.maAverageFontSize ),
    meCharSet( rImplFont.meCharSet ),
    maLanguageTag( rImplFont.maLanguageTag ),
    maCJKLanguageTag( rImplFont.maCJKLanguageTag ),
    mbOutline( rImplFont.mbOutline ),
    mbConfigLookup( rImplFont.mbConfigLookup ),
    mbShadow( rImplFont.mbShadow ),
    mbVertical( rImplFont.mbVertical ),
    mbTransparent( rImplFont.mbTransparent ),
    maColor( rImplFont.maColor ),
    maFillColor( rImplFont.maFillColor ),
    mbWordLine( rImplFont.mbWordLine ),
    mnOrientation( rImplFont.mnOrientation ),
    mnQuality( rImplFont.mnQuality ),
    mnCalculatedAverageFontWidth( rImplFont.mnCalculatedAverageFontWidth )
{}

bool ImplFont::operator==( const ImplFont& rOther ) const
{
    if(!EqualIgnoreColor( rOther ))
        return false;

    if( (maColor        != rOther.maColor)
    ||  (maFillColor    != rOther.maFillColor) )
        return false;

    return true;
}

bool ImplFont::EqualIgnoreColor( const ImplFont& rOther ) const
{
    // equality tests split up for easier debugging
    if( (meWeight   != rOther.meWeight)
    ||  (meItalic   != rOther.meItalic)
    ||  (meFamily   != rOther.meFamily)
    ||  (mePitch    != rOther.mePitch) )
        return false;

    if( (meCharSet        != rOther.meCharSet)
    ||  (maLanguageTag    != rOther.maLanguageTag)
    ||  (maCJKLanguageTag != rOther.maCJKLanguageTag)
    ||  (meAlign          != rOther.meAlign) )
        return false;

    if( (maAverageFontSize       != rOther.maAverageFontSize)
    ||  (mnOrientation  != rOther.mnOrientation)
    ||  (mbVertical     != rOther.mbVertical) )
        return false;

    if( (maFamilyName   != rOther.maFamilyName)
    ||  (maStyleName    != rOther.maStyleName) )
        return false;

    if( (meUnderline    != rOther.meUnderline)
    ||  (meOverline     != rOther.meOverline)
    ||  (meStrikeout    != rOther.meStrikeout)
    ||  (meRelief       != rOther.meRelief)
    ||  (meEmphasisMark != rOther.meEmphasisMark)
    ||  (mbWordLine     != rOther.mbWordLine)
    ||  (mbOutline      != rOther.mbOutline)
    ||  (mbShadow       != rOther.mbShadow)
    ||  (meKerning      != rOther.meKerning)
    ||  (mnSpacing      != rOther.mnSpacing)
    ||  (mbTransparent  != rOther.mbTransparent) )
        return false;

    return true;
}

size_t ImplFont::GetHashValue() const
{
    size_t hash = GetHashValueIgnoreColor();
    o3tl::hash_combine( hash, static_cast<sal_uInt32>( maColor ));
    o3tl::hash_combine( hash, static_cast<sal_uInt32>( maFillColor ));
    return hash;
}

size_t ImplFont::GetHashValueIgnoreColor() const
{
    size_t hash = 0;

    o3tl::hash_combine( hash, meWeight );
    o3tl::hash_combine( hash, meItalic );
    o3tl::hash_combine( hash, meFamily );
    o3tl::hash_combine( hash, mePitch );

    o3tl::hash_combine( hash, meCharSet );
    o3tl::hash_combine( hash, maLanguageTag.getLanguageType( false ).get());
    o3tl::hash_combine( hash, maCJKLanguageTag.getLanguageType( false ).get());
    o3tl::hash_combine( hash, meAlign );

    o3tl::hash_combine( hash, maAverageFontSize.GetHashValue());
    o3tl::hash_combine( hash, mnOrientation.get());
    o3tl::hash_combine( hash, mbVertical );

    o3tl::hash_combine( hash, maFamilyName );
    o3tl::hash_combine( hash, maStyleName );

    o3tl::hash_combine( hash, meUnderline );
    o3tl::hash_combine( hash, meOverline );
    o3tl::hash_combine( hash, meStrikeout );
    o3tl::hash_combine( hash, meRelief );
    o3tl::hash_combine( hash, meEmphasisMark );
    o3tl::hash_combine( hash, mbWordLine );
    o3tl::hash_combine( hash, mbOutline );
    o3tl::hash_combine( hash, mbShadow );
    o3tl::hash_combine( hash, meKerning );
    o3tl::hash_combine( hash, mnSpacing );
    o3tl::hash_combine( hash, mbTransparent );

    return hash;
}

void ImplFont::AskConfig()
{
    if( mbConfigLookup )
        return;

    mbConfigLookup = true;

    // prepare the FontSubst configuration lookup
    const utl::FontSubstConfiguration& rFontSubst = utl::FontSubstConfiguration::get();

    OUString      aShortName;
    OUString      aFamilyName;
    ImplFontAttrs nType = ImplFontAttrs::None;
    FontWeight  eWeight = WEIGHT_DONTKNOW;
    FontWidth   eWidthType = WIDTH_DONTKNOW;
    OUString    aMapName = GetEnglishSearchFontName( maFamilyName );

    utl::FontSubstConfiguration::getMapName( aMapName,
        aShortName, aFamilyName, eWeight, eWidthType, nType );

    // lookup the font name in the configuration
    const utl::FontNameAttr* pFontAttr = rFontSubst.getSubstInfo( aMapName );

    // if the direct lookup failed try again with an alias name
    if ( !pFontAttr && (aShortName != aMapName) )
        pFontAttr = rFontSubst.getSubstInfo( aShortName );

    if( pFontAttr )
    {
        // the font was found in the configuration
        if( meFamily == FAMILY_DONTKNOW )
        {
            if ( pFontAttr->Type & ImplFontAttrs::Serif )
                meFamily = FAMILY_ROMAN;
            else if ( pFontAttr->Type & ImplFontAttrs::SansSerif )
                meFamily = FAMILY_SWISS;
            else if ( pFontAttr->Type & ImplFontAttrs::Typewriter )
                meFamily = FAMILY_MODERN;
            else if ( pFontAttr->Type & ImplFontAttrs::Italic )
                meFamily = FAMILY_SCRIPT;
            else if ( pFontAttr->Type & ImplFontAttrs::Decorative )
                meFamily = FAMILY_DECORATIVE;
        }

        if( mePitch == PITCH_DONTKNOW )
        {
            if ( pFontAttr->Type & ImplFontAttrs::Fixed )
                mePitch = PITCH_FIXED;
        }
    }

    // if some attributes are still unknown then use the FontSubst magic
    if( meFamily == FAMILY_DONTKNOW )
    {
        if( nType & ImplFontAttrs::Serif )
            meFamily = FAMILY_ROMAN;
        else if( nType & ImplFontAttrs::SansSerif )
            meFamily = FAMILY_SWISS;
        else if( nType & ImplFontAttrs::Typewriter )
            meFamily = FAMILY_MODERN;
        else if( nType & ImplFontAttrs::Italic )
            meFamily = FAMILY_SCRIPT;
        else if( nType & ImplFontAttrs::Decorative )
            meFamily = FAMILY_DECORATIVE;
    }

    if( GetWeight() == WEIGHT_DONTKNOW )
        SetWeight( eWeight );
    if( meWidthType == WIDTH_DONTKNOW )
        meWidthType = eWidthType;
}

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