vcl: parser of font features included in the font name

Change-Id: I7347410b4eb5e940d94c34aac4fdf344869541fa
Reviewed-on: https://gerrit.libreoffice.org/55893
Tested-by: Jenkins
Reviewed-by: Tomaž Vajngerl <quikee@gmail.com>
diff --git a/include/vcl/font/FeatureParser.hxx b/include/vcl/font/FeatureParser.hxx
new file mode 100644
index 0000000..595bb03
--- /dev/null
+++ b/include/vcl/font/FeatureParser.hxx
@@ -0,0 +1,52 @@
/* -*- 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/.
 */

#ifndef INCLUDED_VCL_FONT_FEATUREPASER_HXX
#define INCLUDED_VCL_FONT_FEATUREPASER_HXX

#include <vcl/dllapi.h>
#include <rtl/ustring.hxx>
#include <rtl/string.hxx>
#include <memory>
#include <vector>
#include <unordered_map>
#include <vcl/font/Feature.hxx>

namespace vcl
{
namespace font
{
// These must not conflict with font name lists which use ; and ,
constexpr const char FeaturePrefix = ':';
constexpr const char FeatureSeparator = '&';

VCL_DLLPUBLIC OUString trimFontNameFeatures(OUString const& rFontName);

class VCL_DLLPUBLIC FeatureParser
{
private:
    OUString m_sLanguage;
    std::vector<std::pair<sal_uInt32, sal_uInt32>> m_aFeatures;

public:
    FeatureParser(OUString const& sFontName);

    OUString getLanguage() const { return m_sLanguage; }

    std::vector<std::pair<sal_uInt32, sal_uInt32>> getFeatures() const { return m_aFeatures; }

    std::unordered_map<sal_uInt32, sal_uInt32> getFeaturesMap();
};

} // end font namespace
} // end vcl namespace

#endif // INCLUDED_VCL_FONT_FEATUREPASER_HXX

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/Library_vcl.mk b/vcl/Library_vcl.mk
index 58fbfaa..f01302f 100644
--- a/vcl/Library_vcl.mk
+++ b/vcl/Library_vcl.mk
@@ -405,6 +405,7 @@ $(eval $(call gb_Library_add_exception_objects,vcl,\
    vcl/source/filter/wmf/wmfwr \
    vcl/source/font/Feature \
    vcl/source/font/FeatureCollector \
    vcl/source/font/FeatureParser \
    vcl/source/font/OpenTypeFeatureDefinitonList \
    vcl/source/font/PhysicalFontCollection \
    vcl/source/font/PhysicalFontFace \
diff --git a/vcl/qa/cppunit/FontFeatureTest.cxx b/vcl/qa/cppunit/FontFeatureTest.cxx
index 0bb28e3..63cc491 100644
--- a/vcl/qa/cppunit/FontFeatureTest.cxx
+++ b/vcl/qa/cppunit/FontFeatureTest.cxx
@@ -10,8 +10,9 @@
#include <test/bootstrapfixture.hxx>
#include <cppunit/TestAssert.h>
#include <cppunit/TestFixture.h>
#include <vcl/font/Feature.hxx>

#include <vcl/font/Feature.hxx>
#include <vcl/font/FeatureParser.hxx>
#include <vcl/virdev.hxx>
#include <vcl/svapp.hxx>

@@ -24,9 +25,11 @@ public:
    }

    void testGetFontFeatures();
    void testParseFeature();

    CPPUNIT_TEST_SUITE(FontFeatureTest);
    CPPUNIT_TEST(testGetFontFeatures);
    CPPUNIT_TEST(testParseFeature);
    CPPUNIT_TEST_SUITE_END();
};

@@ -103,6 +106,70 @@ void FontFeatureTest::testGetFontFeatures()
    }
}

void FontFeatureTest::testParseFeature()
{
    { // No font features specified
        vcl::font::FeatureParser aParser("Font name with no features");
        CPPUNIT_ASSERT_EQUAL(size_t(0), aParser.getFeatures().size());
    }
    { // One feature specified, no value
        vcl::font::FeatureParser aParser("Font name:abcd");
        CPPUNIT_ASSERT_EQUAL(size_t(1), aParser.getFeatures().size());
        auto aFeatures = aParser.getFeatures();

        CPPUNIT_ASSERT_EQUAL(vcl::font::featureCode("abcd"), aFeatures[0].first);
        CPPUNIT_ASSERT_EQUAL(sal_uInt32(1), aFeatures[0].second);
    }
    { // One feature specified, explicit value
        vcl::font::FeatureParser aParser("Font name:abcd=5");
        CPPUNIT_ASSERT_EQUAL(size_t(1), aParser.getFeatures().size());
        auto aFeatures = aParser.getFeatures();

        CPPUNIT_ASSERT_EQUAL(vcl::font::featureCode("abcd"), aFeatures[0].first);
        CPPUNIT_ASSERT_EQUAL(sal_uInt32(5), aFeatures[0].second);
    }
    { // Multiple features specified, no values
        vcl::font::FeatureParser aParser("Font name:abcd&bcde&efgh");
        CPPUNIT_ASSERT_EQUAL(size_t(3), aParser.getFeatures().size());
        auto aFeatures = aParser.getFeatures();

        CPPUNIT_ASSERT_EQUAL(vcl::font::featureCode("abcd"), aFeatures[0].first);
        CPPUNIT_ASSERT_EQUAL(sal_uInt32(1), aFeatures[0].second);

        CPPUNIT_ASSERT_EQUAL(vcl::font::featureCode("bcde"), aFeatures[1].first);
        CPPUNIT_ASSERT_EQUAL(sal_uInt32(1), aFeatures[1].second);

        CPPUNIT_ASSERT_EQUAL(vcl::font::featureCode("efgh"), aFeatures[2].first);
        CPPUNIT_ASSERT_EQUAL(sal_uInt32(1), aFeatures[2].second);
    }
    {
        // Multiple features specified, explicit values
        // Only 4 char parameter names supported - "toolong" is too long and igoned
        // If value is 0, it should be also ignored
        vcl::font::FeatureParser aParser("Font name:abcd=1&bcde=0&toolong=1&cdef=3");
        CPPUNIT_ASSERT_EQUAL(size_t(2), aParser.getFeatures().size());
        auto aFeatures = aParser.getFeatures();

        CPPUNIT_ASSERT_EQUAL(vcl::font::featureCode("abcd"), aFeatures[0].first);
        CPPUNIT_ASSERT_EQUAL(sal_uInt32(1), aFeatures[0].second);

        CPPUNIT_ASSERT_EQUAL(vcl::font::featureCode("cdef"), aFeatures[1].first);
        CPPUNIT_ASSERT_EQUAL(sal_uInt32(3), aFeatures[1].second);
    }
    {
        // Special case - "lang" is parsed specially and access separately not as a feature.

        vcl::font::FeatureParser aParser("Font name:abcd=1&lang=slo");
        CPPUNIT_ASSERT_EQUAL(size_t(1), aParser.getFeatures().size());
        auto aFeatures = aParser.getFeatures();

        CPPUNIT_ASSERT_EQUAL(vcl::font::featureCode("abcd"), aFeatures[0].first);
        CPPUNIT_ASSERT_EQUAL(sal_uInt32(1), aFeatures[0].second);

        CPPUNIT_ASSERT_EQUAL(OUString("slo"), aParser.getLanguage());
    }
}

CPPUNIT_TEST_SUITE_REGISTRATION(FontFeatureTest);

CPPUNIT_PLUGIN_IMPLEMENT();
diff --git a/vcl/source/font/FeatureParser.cxx b/vcl/source/font/FeatureParser.cxx
new file mode 100644
index 0000000..d61c0a8
--- /dev/null
+++ b/vcl/source/font/FeatureParser.cxx
@@ -0,0 +1,74 @@
/* -*- 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/.
 *
 */

#include <vcl/font/FeatureParser.hxx>

namespace vcl
{
namespace font
{
OUString trimFontNameFeatures(OUString const& rFontName)
{
    OUString sResultName(rFontName);

    if (sResultName.indexOf(vcl::font::FeaturePrefix) < 0)
        return sResultName;

    return sResultName.getToken(0, vcl::font::FeaturePrefix);
}

FeatureParser::FeatureParser(OUString const& rFontName)
{
    if (rFontName.indexOf(vcl::font::FeaturePrefix) < 0)
        return;

    OUString sName = rFontName.getToken(1, vcl::font::FeaturePrefix);
    sal_Int32 nIndex = 0;
    do
    {
        OUString sToken = sName.getToken(0, vcl::font::FeatureSeparator, nIndex);

        OUString sID = sToken.getToken(0, '=');
        OUString sValue = sToken.getToken(1, '=');

        if (sID.getLength() == 4 && sValue != "0")
        {
            if (sID == "lang")
            {
                m_sLanguage = sValue;
            }
            else
            {
                OString sFeatureCodeAscii = OUStringToOString(sID, RTL_TEXTENCODING_ASCII_US);
                sal_uInt32 nCode = vcl::font::featureCode(sFeatureCodeAscii.getStr());
                sal_uInt32 nValue = sValue.isEmpty() ? 1 : sValue.toUInt32();

                if (nValue != 0)
                    m_aFeatures.emplace_back(nCode, nValue);
            }
        }
    } while (nIndex >= 0);
}

std::unordered_map<sal_uInt32, sal_uInt32> FeatureParser::getFeaturesMap()
{
    std::unordered_map<sal_uInt32, sal_uInt32> aResultMap;
    for (auto const& rPair : m_aFeatures)
    {
        aResultMap.emplace(rPair);
    }
    return aResultMap;
}

} // end font namespace

} // end vcl namespace

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/CommonSalLayout.cxx b/vcl/source/gdi/CommonSalLayout.cxx
index 2eff9d9..eb3260b 100644
--- a/vcl/source/gdi/CommonSalLayout.cxx
+++ b/vcl/source/gdi/CommonSalLayout.cxx
@@ -25,6 +25,8 @@

#include <unotools/configmgr.hxx>
#include <vcl/unohelp.hxx>
#include <vcl/font/Feature.hxx>
#include <vcl/font/FeatureParser.hxx>
#include <scrptrun.h>
#include <com/sun/star/i18n/CharacterIteratorMode.hpp>
#include <i18nlangtag/mslangid.hxx>
@@ -66,27 +68,16 @@ GenericSalLayout::~GenericSalLayout()

void GenericSalLayout::ParseFeatures(const OUString& aName)
{
    if (aName.indexOf(FontSelectPatternAttributes::FEAT_PREFIX) < 0)
        return;
    vcl::font::FeatureParser aParser(aName);
    OUString sLanguage = aParser.getLanguage();
    if (!sLanguage.isEmpty())
        msLanguage = OUStringToOString(sLanguage, RTL_TEXTENCODING_ASCII_US);

    OString sName = OUStringToOString(aName, RTL_TEXTENCODING_ASCII_US);
    sName = sName.getToken(1, FontSelectPatternAttributes::FEAT_PREFIX);
    sal_Int32 nIndex = 0;
    do
    for (std::pair<sal_uInt32, sal_uInt32> const & rPair : aParser.getFeatures())
    {
        OString sToken = sName.getToken(0, FontSelectPatternAttributes::FEAT_SEPARATOR, nIndex);
        if (sToken.startsWith("lang="))
        {
            msLanguage = sToken.getToken(1, '=');
        }
        else
        {
            hb_feature_t aFeature;
            if (hb_feature_from_string(sToken.getStr(), sToken.getLength(), &aFeature))
                maFeatures.push_back(aFeature);
        }
        hb_feature_t aFeature { rPair.first, rPair.second, 0, SAL_MAX_UINT32 };
        maFeatures.push_back(aFeature);
    }
    while (nIndex >= 0);
}

struct SubRun