Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/LibreOffice/vcl/win/gdi/   (Office von Apache Version 25.8.3.2©)  Datei vom 5.10.2025 mit Größe 41 kB image not shown  

Quelle  salfont.cxx   Sprache: C

 
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */


#include <sal/config.h>

#include <sal/types.h>
#include <config_folders.h>

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

#include <dwrite_3.h>
#include <o3tl/lru_map.hxx>
#include <basegfx/matrix/b2dhommatrixtools.hxx>
#include <basegfx/polygon/b2dpolygon.hxx>
#include <i18nlangtag/mslangid.hxx>
#include <osl/diagnose.h>
#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 <tools/urlobj.hxx>
#include <unotools/fontcfg.hxx>
#include <vcl/settings.hxx>
#include <vcl/sysdata.hxx>
#include <vcl/metric.hxx>
#include <vcl/fontcharmap.hxx>
#include <comphelper/scopeguard.hxx>
#include <comphelper/windowserrorstring.hxx>

#include <font/FontSelectPattern.hxx>
#include <font/PhysicalFontCollection.hxx>
#include <font/PhysicalFontFaceCollection.hxx>
#include <font/PhysicalFontFace.hxx>
#include <font/fontsubstitution.hxx>
#include <sft.hxx>
#include <win/saldata.hxx>
#include <win/salgdi.h>
#include <win/winlayout.hxx>
#include <win/wingdiimpl.hxx>
#include <impfontcharmap.hxx>
#include <font/FontMetricData.hxx>
#include <impglyphitem.hxx>

#if HAVE_FEATURE_SKIA
#include <vcl/skia/SkiaHelper.hxx>
#include <skia/win/font.hxx>
#endif

using namespace vcl;

static FIXED FixedFromDouble( double d )
{
    const tools::Long l = static_cast<tools::Long>( d * 65536. );
    return *reinterpret_cast<FIXED const *>(&l);
}

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

// platform specific font substitution hooks for glyph fallback enhancement

namespace {

class WinPreMatchFontSubstititution
:    public vcl::font::PreMatchFontSubstitution
{
public:
    bool FindFontSubstitute(vcl::font::FontSelectPattern&) const override;
};

class WinGlyphFallbackSubstititution
:    public vcl::font::GlyphFallbackFontSubstitution
{
public:
    bool FindFontSubstitute(vcl::font::FontSelectPattern&, LogicalFontInstance* pLogicalFont, OUString& rMissingChars) const override;
};

// does a font face hold the given missing characters?
bool HasMissingChars(vcl::font::PhysicalFontFace* pFace, OUString& rMissingChars)
{
    FontCharMapRef xFontCharMap = pFace->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;
}

    //used by 2-level font fallback
    vcl::font::PhysicalFontFamily* findDevFontListByLocale(const vcl::font::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
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(vcl::font::FontSelectPattern& rFontSelData) const
{
    if (rFontSelData.IsMicrosoftSymbolEncoded() || IsOpenSymbol(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(vcl::font::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 vcl::font::PhysicalFontCollection* pFontCollection = ImplGetSVData()->maGDIData.mxScreenFontList.get();
    vcl::font::PhysicalFontFamily* pFontFamily = findDevFontListByLocale(*pFontCollection, aLanguageTag);
    if( pFontFamily )
    {
        vcl::font::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 )
    {
        vcl::font::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<vcl::font::PhysicalFontFaceCollection> pTestFontList = pFontCollection->GetFontFaceCollection();
    // 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;
    forint i = 0; i < nTestFontCount; ++i )
    {
        vcl::font::PhysicalFontFace* pFace = pTestFontList->Get( i );
        bFound = HasMissingChars( pFace, rMissingChars );
        if( !bFound )
            continue;
        rFontSelData.maSearchName = pFace->GetFamilyName();
        break;
    }

    return bFound;
}

namespace {

struct ImplEnumInfo
{
    HDC                 mhDC;
    vcl::font::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;
}

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

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

static 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.SetMicrosoftSymbolEncoded(rLogFont.lfCharSet == SYMBOL_CHARSET);

    // get the font face name
    aDFA.SetFamilyName(OUString(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(OUString(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;
}

void ImplSalLogFontToFontW( HDC hDC, const LOGFONTW& rLogFont, Font& rFont )
{
    OUString aFontName( o3tl::toU(rLogFont.lfFaceName) );
    if (aFontName.isEmpty())
        return;

    rFont.SetFamilyName( aFontName );
    rFont.SetCharSet( ImplCharSetToSal( rLogFont.lfCharSet ) );
    rFont.SetFamily( ImplFamilyToSal( rLogFont.lfPitchAndFamily ) );
    rFont.SetPitch( ImplLogPitchToSal( rLogFont.lfPitchAndFamily ) );
    rFont.SetWeight( ImplWeightToSal( rLogFont.lfWeight ) );

    tools::Long nFontHeight = rLogFont.lfHeight;
    if ( nFontHeight < 0 )
        nFontHeight = -nFontHeight;
    tools::Long nDPIY = GetDeviceCaps( hDC, LOGPIXELSY );
    if( !nDPIY )
        nDPIY = 600;
    nFontHeight *= 72;
    nFontHeight += nDPIY/2;
    nFontHeight /= nDPIY;
    rFont.SetFontSize( Size( 0, nFontHeight ) );
    rFont.SetOrientation( Degree10(static_cast<sal_Int16>(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 ENUMLOGFONTEXW& rEnumFont, const NEWTEXTMETRICW&&nbsp;rMetric)
:   vcl::font::PhysicalFontFace(WinFont2DevFontAttributes(rEnumFont, rMetric)),
    mnId( 0 ),
    meWinCharSet(rEnumFont.elfLogFont.lfCharSet),
    mnPitchAndFamily(rMetric.tmPitchAndFamily),
    maLogFont(rEnumFont.elfLogFont)
{
}

WinFontFace::~WinFontFace()
{
}

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

rtl::Reference<LogicalFontInstance> WinFontFace::CreateFontInstance(const vcl::font::FontSelectPattern& rFSD) const
{
#if HAVE_FEATURE_SKIA
    if (SkiaHelper::isVCLSkiaEnabled())
        return new SkiaWinFontInstance(*this, rFSD);
#endif
    return new WinFontInstance(*this, rFSD);
}

const std::vector<hb_variation_t>&
WinFontFace::GetVariations(const LogicalFontInstance& rFont) const
{
    if (!mxVariations)
    {
        mxVariations.emplace();
        auto pDWFontFace = static_cast<const WinFontInstance&>(rFont).GetDWFontFace();
        if (pDWFontFace)
        {
            sal::systools::COMReference<IDWriteFontFace5> xDWFontFace5;
            auto hr = pDWFontFace->QueryInterface(__uuidof(IDWriteFontFace5),
                                                  reinterpret_cast<void**>(&xDWFontFace5));
            if (SUCCEEDED(hr) && xDWFontFace5->HasVariations())
            {
                std::vector<DWRITE_FONT_AXIS_VALUE> aAxisValues(
                    xDWFontFace5->GetFontAxisValueCount());
                hr = xDWFontFace5->GetFontAxisValues(aAxisValues.data(), aAxisValues.size());
                if (SUCCEEDED(hr))
                {
                    mxVariations->reserve(aAxisValues.size());
                    for (auto& rAxisValue : aAxisValues)
                        mxVariations->push_back(
                            { OSL_NETDWORD(rAxisValue.axisTag), rAxisValue.value });
                }
            }
        }
    }

    return *mxVariations;
}

namespace
{
struct BlobReference
{
    hb_blob_t* mpBlob;
    BlobReference(hb_blob_t* pBlob)
        : mpBlob(pBlob)
    {
        hb_blob_reference(mpBlob);
    }
    BlobReference(BlobReference&& other) noexcept
        : mpBlob(other.mpBlob)
    {
        other.mpBlob = nullptr;
    }
    BlobReference& operator=(BlobReference&& other)
    {
        std::swap(mpBlob, other.mpBlob);
        return *this;
    }
    BlobReference(const BlobReference& other) = delete;
    BlobReference& operator=(BlobReference& other) = delete;
    ~BlobReference() { hb_blob_destroy(mpBlob); }
};
}

using BlobCacheKey = std::pair<sal_IntPtr, hb_tag_t>;

namespace
{
struct BlobCacheKeyHash
{
    std::size_t operator()(BlobCacheKey const& rKey) const
    {
        std::size_t seed = 0;
        o3tl::hash_combine(seed, rKey.first);
        o3tl::hash_combine(seed, rKey.second);
        return seed;
    }
};
}

hb_blob_t* WinFontFace::GetHbTable(hb_tag_t nTag) const
{
    static o3tl::lru_map<BlobCacheKey, BlobReference, BlobCacheKeyHash> gCache(50);
    BlobCacheKey aCacheKey{ GetFontId(), nTag };
    auto it = gCache.find(aCacheKey);
    if (it != gCache.end())
    {
        hb_blob_reference(it->second.mpBlob);
        return it->second.mpBlob;
    }

    sal_uLong nLength = 0;
    unsigned char* pBuffer = nullptr;

    HDC hDC(::GetDC(nullptr));
    HFONT hFont = ::CreateFontIndirectW(&maLogFont);
    HFONT hOldFont = ::SelectFont(hDC, hFont);

    nLength = ::GetFontData(hDC, OSL_NETDWORD(nTag), 0, nullptr, 0);
    if (nLength > 0 && nLength != GDI_ERROR)
    {
        pBuffer = new unsigned char[nLength];
        ::GetFontData(hDC, OSL_NETDWORD(nTag), 0, pBuffer, nLength);
    }

    ::SelectFont(hDC, hOldFont);
    ::DeleteFont(hFont);
    ::ReleaseDC(nullptr, hDC);

    hb_blob_t* pBlob = nullptr;

    if (pBuffer)
        pBlob = hb_blob_create(reinterpret_cast<const char*>(pBuffer), nLength, HB_MEMORY_MODE_READONLY,
                               pBuffer, [](void* data) { delete[] static_cast<unsigned char*>(data); });

    gCache.insert({ aCacheKey, BlobReference(pBlob) });
    return pBlob;
}

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

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

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

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

    UINT nNameLen = aName.getLength();
    if (nNameLen >= LF_FACESIZE)
        nNameLen = LF_FACESIZE - 1;
    memcpy( rLogFont.lfFaceName, aName.getStr(), nNameLen*sizeofwchar_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.IsMicrosoftSymbolEncoded() ? SYMBOL_CHARSET : DEFAULT_CHARSET;
        rLogFont.lfPitchAndFamily = ImplPitchToWin( rFont.GetPitch() )
                                  | ImplFamilyToWin( rFont.GetFamilyType() );
    }

    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.get();
    rLogFont.lfOrientation     = rLogFont.lfEscapement;
    rLogFont.lfClipPrecision   = CLIP_DEFAULT_PRECIS;
    rLogFont.lfOutPrecision    = OUT_TT_PRECIS;
    if ( rFont.mnOrientation )
        rLogFont.lfClipPrecision |= CLIP_LH_ANGLES;

    // disable antialiasing if requested
    if ( rFont.mbNonAntialiased )
        rLogFont.lfQuality = NONANTIALIASED_QUALITY;
    else if (Application::GetSettings().GetStyleSettings().GetUseFontAAFromSystem())
        rLogFont.lfQuality = DEFAULT_QUALITY; // for display on screen
    else
        rLogFont.lfQuality = bAntiAliased ? ANTIALIASED_QUALITY : NONANTIALIASED_QUALITY;
}

std::tuple<HFONT, HFONT, sal_Int32>
WinSalGraphics::ImplDoSetFont(HDC hDC, vcl::font::FontSelectPattern const& i_rFont,
                              const vcl::font::PhysicalFontFace* i_pFontFace, HFONT& o_rOldFont)
{
    HFONT hNewFont = nullptr;

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

    hNewFont = ::CreateFontIndirectW( &aLogFont );
    o_rOldFont = ::SelectFont(hDC, hNewFont);

    TEXTMETRICW aTextMetricW;
    if (!::GetTextMetricsW(hDC, &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(hDC, hNewFont2);
        DeleteFont( hNewFont );
        hNewFont = hNewFont2;
    }

    // Optionally create a secondary font for non-rotated CJK glyphs in vertical context
    HFONT hNewVerticalFont = nullptr;
    if (i_rFont.mbVertical && mbPrinter)
    {
        aLogFont.lfEscapement = 0;
        aLogFont.lfOrientation = 0;
        hNewVerticalFont = ::CreateFontIndirectW(&aLogFont);
    }

    return std::make_tuple(hNewFont, hNewVerticalFont,
                           static_cast<sal_Int32>(aTextMetricW.tmDescent));
}

void WinSalGraphics::SetFont(LogicalFontInstance* pFont, int nFallbackLevel)
{
    assert(nFallbackLevel >= 0 && nFallbackLevel < MAX_FALLBACK);

    // return early if there is no new font
    if( !pFont )
    {
        if (!mpWinFontEntry[nFallbackLevel].is())
            return;

        // DeInitGraphics doesn't free the cached fonts, so mhDefFont might be nullptr
        if (mhDefFont)
        {
            ::SelectFont(getHDC(), mhDefFont);
            mhDefFont = nullptr;
        }

        // release no longer referenced font handles
        forint 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
        forint i = nFallbackLevel + 1; i < MAX_FALLBACK && mpWinFontEntry[i].is(); ++i )
            mpWinFontEntry[i] = nullptr;
    }
}

void WinSalGraphics::GetFontMetric( FontMetricDataRef& 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(OUString(o3tl::toU(aFaceName)));

    rxFontMetric->SetMinKashida(pFontInstance->GetKashidaWidth());
    rxFontMetric->ImplCalcLineSpacing(pFontInstance.get());
    rxFontMetric->ImplInitBaselines(pFontInstance.get());

    // 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->SetMicrosoftSymbolEncoded(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(aWinMetric.tmAveCharWidth);
}

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

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

static 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_INFO("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_INFO("vcl.fonts""Unsupported font ignored: " << OUString(o3tl::toU(pLogFont->elfLogFont.lfFaceName)));
            return 1;
        }

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

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

    return 1;
}

struct TempFontItem
{
    OUString maFontResourcePath;
    TempFontItem* mpNextItem;
};

static int lcl_AddFontResource(SalData& rSalData, const OUString& rFontFileURL)
{
    OUString aFontSystemPath;
    OSL_VERIFY(!osl::FileBase::getSystemPathFromFileURL(rFontFileURL, aFontSystemPath));

    int nRet = AddFontResourceExW(o3tl::toW(aFontSystemPath.getStr()), FR_PRIVATE, nullptr);
    SAL_WARN_IF(nRet <= 0, "vcl.fonts""AddFontResourceExW failed for " << rFontFileURL);
    if (nRet <= 0)
        return nRet;

    TempFontItem* pNewItem = new TempFontItem;
    pNewItem->maFontResourcePath = aFontSystemPath;

    pNewItem->mpNextItem = rSalData.mpTempFontItem;
    rSalData.mpTempFontItem = pNewItem;

    return nRet;
}

void ImplReleaseTempFonts(SalData& rSalData)
{
    while (TempFontItem* p = rSalData.mpTempFontItem)
    {
        RemoveFontResourceExW(o3tl::toW(p->maFontResourcePath.getStr()), FR_PRIVATE, nullptr);
        rSalData.mpTempFontItem = p->mpNextItem;
        delete p;
    }
}

bool WinSalGraphics::AddTempDevFont(vcl::font::PhysicalFontCollection* pFontCollection,
                                    const OUString& rFontFileURL, const OUString& rFontName)
{
    OUString aFontFamily = getFontFamilyNameFromTTF(rFontFileURL);
    if (aFontFamily.isEmpty())
    {
        SAL_WARN("vcl.fonts""error extracting font family from " << rFontFileURL);
        return false;
    }

    if (rFontName != aFontFamily)
    {
        SAL_WARN("vcl.fonts""font family renaming not implemented; skipping embedded " << rFontName);
        return false;
    }

    int nFonts = lcl_AddFontResource(*GetSalData(), rFontFileURL);
    if (nFonts <= 0)
        return false;

    ImplEnumInfo aInfo;
    aInfo.mhDC = getHDC();
    aInfo.mpList = pFontCollection;
    aInfo.mpName = &aFontFamily;
    aInfo.mbPrinter = mbPrinter;
    aInfo.mnFontCount = pFontCollection->Count();
    const int nExpectedFontCount = aInfo.mnFontCount + nFonts;

    LOGFONTW aLogFont = {};
    aLogFont.lfCharSet = DEFAULT_CHARSET;
    aInfo.mpLogFont = &aLogFont;

    // add the font to the PhysicalFontCollection
    EnumFontFamiliesExW(getHDC(), &aLogFont,
        SalEnumFontsProcExW, reinterpret_cast<LPARAM>(&aInfo), 0);

    SAL_WARN_IF(nExpectedFontCount != pFontCollection->Count(), "vcl.fonts",
        "temp font was registered but is not in enumeration: " << rFontFileURL);

    return true;
}

void WinSalGraphics::GetDevFontList( vcl::font::PhysicalFontCollection* pFontCollection )
{
    // make sure all LO shared fonts are registered temporarily
    static std::once_flag init;
    std::call_once(init, []()
    {
        auto registerFontsIn = [](const OUString& dir) {
            // collect fonts in font path that could not be registered
            osl::Directory aFontDir(dir);
            osl::FileBase::RC rcOSL = aFontDir.open();
            if (rcOSL == osl::FileBase::E_None)
            {
                osl::DirectoryItem aDirItem;
                SalData* pSalData = GetSalData();
                assert(pSalData);

                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)
                        lcl_AddFontResource(*pSalData, aFileStatus.getFileURL());
                }
            }
        };

        // 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);

        // internal font resources, required for normal operation, like OpenSymbol
        registerFontsIn(aPath + "/" LIBO_SHARE_RESOURCE_FOLDER "/common/fonts");

        // collect fonts in font path that could not be registered
        registerFontsIn(aPath + "/" LIBO_SHARE_FOLDER "/fonts/truetype");

        return true;
    });

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

    LOGFONTW aLogFont = {};
    aLogFont.lfCharSet = DEFAULT_CHARSET;
    aInfo.mpLogFont = &aLogFont;

    // fill the PhysicalFontCollection
    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);
}

void WinSalGraphics::ClearDevFontCache()
{
    mWinSalGraphicsImplBase->ClearDevFontCache();
}

bool WinFontInstance::GetGlyphOutline(sal_GlyphId nId, basegfx::B2DPolyPolygon& rB2DPolyPoly, boolconst
{
    rB2DPolyPoly.clear();

    assert(m_pGraphics);
    HDC hDC = m_pGraphics->getHDC();
    const HFONT hOrigFont = static_cast<HFONT>(GetCurrentObject(hDC, OBJ_FONT));
    const HFONT hFont = GetHFONT();
    if (hFont != hOrigFont)
        SelectObject(hDC, hFont);

    const ::comphelper::ScopeGuard aFontRestoreScopeGuard([hFont, hOrigFont, hDC]()
        { if (hFont != hOrigFont) SelectObject(hDC, hOrigFont); });

    // 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, nId, 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, nId, 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);
    whilereinterpret_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;

        tools::Long nX = IntTimes256FromFixed( pHeader->pfxStart.x );
        tools::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 );
        whilereinterpret_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
        forint 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() )
    {
        const double fFactor(1.0f/256);
        rB2DPolyPoly.transform(basegfx::utils::createScaleB2DHomMatrix(fFactor, fFactor));
    }

    return true;
}

IDWriteFontFace* WinFontInstance::GetDWFontFace() const
{
    if (!mxDWFontFace)
    {
        assert(m_pGraphics);
        HDC hDC = m_pGraphics->getHDC();
        const HFONT hOrigFont = static_cast<HFONT>(GetCurrentObject(hDC, OBJ_FONT));
        const HFONT hFont = GetHFONT();
        if (hFont != hOrigFont)
            SelectObject(hDC, hFont);

        const ::comphelper::ScopeGuard aFontRestoreScopeGuard([hFont, hOrigFont, hDC]() {
            if (hFont != hOrigFont)
                SelectObject(hDC, hOrigFont);
        });

        IDWriteGdiInterop* pDWriteGdiInterop = WinSalGraphics::getDWriteGdiInterop();

        HRESULT hr = pDWriteGdiInterop->CreateFontFaceFromHdc(hDC, &mxDWFontFace);
        if (FAILED(hr))
        {
            SAL_WARN("vcl.fonts""HRESULT 0x" << OUString::number(hr, 16) << ": "
                                               << comphelper::WindowsErrorStringFromHRESULT(hr));
            mxDWFontFace = nullptr;
        }
    }

    return mxDWFontFace;
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */

Messung V0.5
C=91 H=95 G=92

¤ Dauer der Verarbeitung: 0.11 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.