Blob Blame History Raw
From 96012f88aac95147ae1fd4834cea5c5bb184d52b Mon Sep 17 00:00:00 2001
From: Khaled Hosny <khaledhosny@eglug.org>
Date: Tue, 27 Aug 2019 15:19:15 +0200
Subject: [PATCH] Make Noto Color Emoji font work on Linux
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Noto Color Emoji is a bitmap color font, Cairo knows how to scale such
fonts and FontConfig will identify them as scalable but not outline
fonts, so change the FontConfig checks to checks for scalability.

Make sft.cxx:doOpenTTFont() accept non-outline fonts, the text will not
show in PDF but that is not worse than the status quo.

Reviewed-on: https://gerrit.libreoffice.org/78218
Tested-by: Jenkins
Reviewed-by: Khaled Hosny <khaledhosny@eglug.org>
(cherry picked from commit dcf7792da2aa2a1ef774a124f7b21f68fff0fd15)

Change-Id: I756c718296d2c43e3165cd2f07b11bbb981318d3

Related: rhbz#1648281 improve fontconfig fallback for emojis

disregard text language for emoji and tag with und-zsye to
get fontconfig to give us the default emoji font

Change-Id: I8f94b0c41dea3204c9db77b96ad8f0d98bae2239

ctrl+shift+e emoji ibus engine problems converting UCS-4 positions to UTF-16

e.g. ctrl+shift+e type rabbit then space in writer and the len of underline
is 2 which should encompass the displayed e + 2 UTF-16 units

Change-Id: I424db7dd6cbcc5845922ac17208fed643e672dbd

rework IM underline impl wrt mix of UTF-8/16/32 units

e.g. ctrl+shift+e type boy then space twice in writer. The UTF-32 units
are 0x65 0x1f466 0x1f3fb. The underline should encompass the whole range,
prior to this the trailing Emoji Modifier Fitzpatrick was separated from
the boy base emoji by an incomplete underline

Reviewed-on: https://gerrit.libreoffice.org/78878
Tested-by: Jenkins
Reviewed-by: Caolán McNamara <caolanm@redhat.com>
Tested-by: Caolán McNamara <caolanm@redhat.com>
(cherry picked from commit 5e4d564e27d062a48fd04cb7263b769819dd3a50)

Change-Id: I2e846e8eeedf96f341ed7f50d504883768e9eff0
---
 vcl/source/font/fontmetric.cxx                |  4 +-
 vcl/source/fontsubset/sft.cxx                 |  5 +-
 vcl/unx/generic/fontmanager/fontconfig.cxx    | 60 +++++++++--------
 .../generic/glyphs/freetype_glyphcache.cxx    |  6 +-
 vcl/unx/gtk3/gtk3gtkframe.cxx                 | 64 +++++++++++++------
 5 files changed, 87 insertions(+), 52 deletions(-)

diff --git a/vcl/source/font/fontmetric.cxx b/vcl/source/font/fontmetric.cxx
index cd0b9f8557e9..816525c8773e 100644
--- a/vcl/source/font/fontmetric.cxx
+++ b/vcl/source/font/fontmetric.cxx
@@ -462,8 +462,8 @@ void ImplFontMetricData::ImplCalcLineSpacing(const std::vector<uint8_t>& rHheaDa
     if (mnAscent || mnDescent)
         mnIntLeading = mnAscent + mnDescent - mnHeight;
 
-    SAL_INFO("vcl.gdi.fontmetric",
-                  "fsSelection: "   << rInfo.fsSelection
+    SAL_INFO("vcl.gdi.fontmetric", GetFamilyName()
+             << ": fsSelection: "   << rInfo.fsSelection
              << ", typoAscender: "  << rInfo.typoAscender
              << ", typoDescender: " << rInfo.typoDescender
              << ", typoLineGap: "   << rInfo.typoLineGap
diff --git a/vcl/source/fontsubset/sft.cxx b/vcl/source/fontsubset/sft.cxx
index 365b9401b95e..04921294ab21 100644
--- a/vcl/source/fontsubset/sft.cxx
+++ b/vcl/source/fontsubset/sft.cxx
@@ -1666,7 +1666,10 @@ static int doOpenTTFont( sal_uInt32 facenum, TrueTypeFont* t )
         /* TODO: implement to get subsetting */
         assert(t->goffsets != nullptr);
     } else {
-        return SF_TTFORMAT;
+        // Bitmap font, accept for now.
+        t->goffsets = static_cast<sal_uInt32 *>(calloc(1+t->nglyphs, sizeof(sal_uInt32)));
+        /* TODO: implement to get subsetting */
+        assert(t->goffsets != nullptr);
     }
 
     table = getTable(t, O_hhea);
diff --git a/vcl/unx/generic/fontmanager/fontconfig.cxx b/vcl/unx/generic/fontmanager/fontconfig.cxx
index 2c16e040cdab..33c50d082912 100644
--- a/vcl/unx/generic/fontmanager/fontconfig.cxx
+++ b/vcl/unx/generic/fontmanager/fontconfig.cxx
@@ -67,7 +67,7 @@ namespace
 
 class FontCfgWrapper
 {
-    FcFontSet* m_pOutlineSet;
+    FcFontSet* m_pFontSet;
 
     void addFontSet( FcSetName );
 
@@ -95,19 +95,15 @@ private:
 };
 
 FontCfgWrapper::FontCfgWrapper()
-    :
-        m_pOutlineSet( nullptr ),
-        m_pLanguageTag( nullptr )
+    : m_pFontSet(nullptr)
+    , m_pLanguageTag(nullptr)
 {
     FcInit();
 }
 
 void FontCfgWrapper::addFontSet( FcSetName eSetName )
 {
-    /*
-      add only acceptable outlined fonts to our config,
-      for future fontconfig use
-    */
+    // Add only acceptable fonts to our config, for future fontconfig use.
     FcFontSet* pOrig = FcConfigGetFonts( FcConfigGetCurrent(), eSetName );
     if( !pOrig )
         return;
@@ -116,10 +112,12 @@ void FontCfgWrapper::addFontSet( FcSetName eSetName )
     for( int i = 0; i < pOrig->nfont; ++i )
     {
         FcPattern* pPattern = pOrig->fonts[i];
-        // #i115131# ignore non-outline fonts
-        FcBool bOutline = FcFalse;
-        FcResult eOutRes = FcPatternGetBool( pPattern, FC_OUTLINE, 0, &bOutline );
-        if( (eOutRes != FcResultMatch) || (bOutline == FcFalse) )
+        // #i115131# ignore non-scalable fonts
+        // Scalable fonts are usually outline fonts, but some bitmaps fonts
+        // (like Noto Color Emoji) are also scalable.
+        FcBool bScalable = FcFalse;
+        FcResult eScalableRes = FcPatternGetBool(pPattern, FC_SCALABLE, 0, &bScalable);
+        if ((eScalableRes != FcResultMatch) || (bScalable == FcFalse))
             continue;
 
         // Ignore Type 1 fonts, too.
@@ -129,7 +127,7 @@ void FontCfgWrapper::addFontSet( FcSetName eSetName )
             continue;
 
         FcPatternReference( pPattern );
-        FcFontSetAdd( m_pOutlineSet, pPattern );
+        FcFontSetAdd( m_pFontSet, pPattern );
     }
 
     // TODO?: FcFontSetDestroy( pOrig );
@@ -220,16 +218,16 @@ namespace
 
 FcFontSet* FontCfgWrapper::getFontSet()
 {
-    if( !m_pOutlineSet )
+    if( !m_pFontSet )
     {
-        m_pOutlineSet = FcFontSetCreate();
+        m_pFontSet = FcFontSetCreate();
         addFontSet( FcSetSystem );
         addFontSet( FcSetApplication );
 
-        ::std::sort(m_pOutlineSet->fonts,m_pOutlineSet->fonts+m_pOutlineSet->nfont,SortFont());
+        ::std::sort(m_pFontSet->fonts,m_pFontSet->fonts+m_pFontSet->nfont,SortFont());
     }
 
-    return m_pOutlineSet;
+    return m_pFontSet;
 }
 
 FontCfgWrapper::~FontCfgWrapper()
@@ -376,10 +374,10 @@ void FontCfgWrapper::clear()
 {
     m_aFontNameToLocalized.clear();
     m_aLocalizedToCanonical.clear();
-    if( m_pOutlineSet )
+    if( m_pFontSet )
     {
-        FcFontSetDestroy( m_pOutlineSet );
-        m_pOutlineSet = nullptr;
+        FcFontSetDestroy( m_pFontSet );
+        m_pFontSet = nullptr;
     }
     delete m_pLanguageTag;
     m_pLanguageTag = nullptr;
@@ -499,7 +497,7 @@ void PrintFontManager::countFontconfigFonts( std::unordered_map<OString, int>& o
             int width = 0;
             int spacing = 0;
             int nCollectionEntry = -1;
-            FcBool outline = false;
+            FcBool scalable = false;
 
             FcResult eFileRes         = FcPatternGetString(pFSet->fonts[i], FC_FILE, 0, &file);
             FcResult eFamilyRes       = rWrapper.LocalizedElementFromPattern( pFSet->fonts[i], &family, FC_FAMILY, FC_FAMILYLANG );
@@ -510,11 +508,11 @@ void PrintFontManager::countFontconfigFonts( std::unordered_map<OString, int>& o
             FcResult eWeightRes       = FcPatternGetInteger(pFSet->fonts[i], FC_WEIGHT, 0, &weight);
             FcResult eWidthRes        = FcPatternGetInteger(pFSet->fonts[i], FC_WIDTH, 0, &width);
             FcResult eSpacRes         = FcPatternGetInteger(pFSet->fonts[i], FC_SPACING, 0, &spacing);
-            FcResult eOutRes          = FcPatternGetBool(pFSet->fonts[i], FC_OUTLINE, 0, &outline);
+            FcResult eScalableRes     = FcPatternGetBool(pFSet->fonts[i], FC_SCALABLE, 0, &scalable);
             FcResult eIndexRes        = FcPatternGetInteger(pFSet->fonts[i], FC_INDEX, 0, &nCollectionEntry);
             FcResult eFormatRes       = FcPatternGetString(pFSet->fonts[i], FC_FONTFORMAT, 0, &format);
 
-            if( eFileRes != FcResultMatch || eFamilyRes != FcResultMatch || eOutRes != FcResultMatch )
+            if( eFileRes != FcResultMatch || eFamilyRes != FcResultMatch || eScalableRes != FcResultMatch )
                 continue;
 
 #if (OSL_DEBUG_LEVEL > 2)
@@ -528,14 +526,15 @@ void PrintFontManager::countFontconfigFonts( std::unordered_map<OString, int>& o
                      , eWeightRes == FcResultMatch ? width : -1
                      , eSpacRes == FcResultMatch ? spacing : -1
                      , eOutRes == FcResultMatch ? outline : -1
+                     , eScalableRes == FcResultMatch ? scalable : -1
                      , eFormatRes == FcResultMatch ? (const char*)format : "<unknown>"
                      );
 #endif
 
-//            OSL_ASSERT(eOutRes != FcResultMatch || outline);
+//            OSL_ASSERT(eScalableRes != FcResultMatch || scalable);
 
-            // only outline fonts are usable to psprint anyway
-            if( eOutRes == FcResultMatch && ! outline )
+            // only scalable fonts are usable to psprint anyway
+            if( eScalableRes == FcResultMatch && ! scalable )
                 continue;
 
             if (isPreviouslyDuplicateOrObsoleted(pFSet, i))
@@ -807,6 +806,11 @@ namespace
 #endif
     }
 
+    bool isEmoji(sal_uInt32 nCurrentChar)
+    {
+        return u_hasBinaryProperty(nCurrentChar, UCHAR_EMOJI);
+    }
+
     //returns true if the given code-point couldn't possibly be in rLangTag.
     bool isImpossibleCodePointForLang(const LanguageTag &rLangTag, sal_uInt32 currentChar)
     {
@@ -855,6 +859,8 @@ namespace
 
     OUString getExemplarLangTagForCodePoint(sal_uInt32 currentChar)
     {
+        if (isEmoji(currentChar))
+            return "und-zsye";
         int32_t script = u_getIntPropertyValue(currentChar, UCHAR_SCRIPT);
         UScriptCode eScript = static_cast<UScriptCode>(script);
         OStringBuffer aBuf(unicode::getExemplarLanguageForUScriptCode(eScript));
@@ -981,7 +987,7 @@ void PrintFontManager::Substitute( FontSelectPattern &rPattern, OUString& rMissi
             FcCharSetAddChar( codePoints, nCode );
             //if the codepoint is impossible for this lang tag, then clear it
             //and autodetect something useful
-            if (!aLangAttrib.isEmpty() && isImpossibleCodePointForLang(aLangTag, nCode))
+            if (!aLangAttrib.isEmpty() && (isImpossibleCodePointForLang(aLangTag, nCode) || isEmoji(nCode)))
                 aLangAttrib.clear();
             //#i105784#/rhbz#527719  improve selection of fallback font
             if (aLangAttrib.isEmpty())
diff --git a/vcl/unx/generic/glyphs/freetype_glyphcache.cxx b/vcl/unx/generic/glyphs/freetype_glyphcache.cxx
index 5a55ee47bff3..0b03f428c3fa 100644
--- a/vcl/unx/generic/glyphs/freetype_glyphcache.cxx
+++ b/vcl/unx/generic/glyphs/freetype_glyphcache.cxx
@@ -409,9 +409,9 @@ FreetypeFont::FreetypeFont( const FontSelectPattern& rFSD, FreetypeFontInfo* pFI
 
     FT_New_Size( maFaceFT, &maSizeFT );
     FT_Activate_Size( maSizeFT );
-    FT_Error rc = FT_Set_Pixel_Sizes( maFaceFT, mnWidth, rFSD.mnHeight );
-    if( rc != FT_Err_Ok )
-        return;
+    /* This might fail for color bitmap fonts, but that is fine since we will
+     * not need any glyph data from FreeType in this case */
+    /*FT_Error rc = */ FT_Set_Pixel_Sizes( maFaceFT, mnWidth, rFSD.mnHeight );
 
     FT_Select_Charmap(maFaceFT, FT_ENCODING_UNICODE);
 
diff --git a/vcl/unx/gtk3/gtk3gtkframe.cxx b/vcl/unx/gtk3/gtk3gtkframe.cxx
index 4ee63a98da95..2f80d03f542b 100644
--- a/vcl/unx/gtk3/gtk3gtkframe.cxx
+++ b/vcl/unx/gtk3/gtk3gtkframe.cxx
@@ -4031,34 +4031,59 @@ void GtkSalFrame::IMHandler::signalIMPreeditChanged( GtkIMContext*, gpointer im_
     pThis->m_bPreeditJustChanged = true;
 
     bool bEndPreedit = (!pText || !*pText) && pThis->m_aInputEvent.mpTextAttr != nullptr;
-    pThis->m_aInputEvent.maText             = pText ? OUString( pText, strlen(pText), RTL_TEXTENCODING_UTF8 ) : OUString();
-    pThis->m_aInputEvent.mnCursorPos        = nCursorPos;
-    pThis->m_aInputEvent.mnCursorFlags      = 0;
+    gint nUtf8Len = pText ? strlen(pText) : 0;
+    pThis->m_aInputEvent.maText             = pText ? OUString(pText, nUtf8Len, RTL_TEXTENCODING_UTF8) : OUString();
+    const OUString& rText = pThis->m_aInputEvent.maText;
 
-    pThis->m_aInputFlags = std::vector<ExtTextInputAttr>( std::max( 1, (int)pThis->m_aInputEvent.maText.getLength() ), ExtTextInputAttr::NONE );
+    std::vector<sal_Int32> aUtf16Offsets;
+    for (sal_Int32 nUtf16Offset = 0; nUtf16Offset < rText.getLength(); rText.iterateCodePoints(&nUtf16Offset))
+        aUtf16Offsets.push_back(nUtf16Offset);
+
+    sal_Int32 nUtf32Len = aUtf16Offsets.size();
+    aUtf16Offsets.push_back(rText.getLength());
+
+    // sanitize the CurPos which is in utf-32
+    if (nCursorPos < 0)
+        nCursorPos = 0;
+    else if (nCursorPos > nUtf32Len)
+        nCursorPos = nUtf32Len;
+
+    pThis->m_aInputEvent.mnCursorPos = aUtf16Offsets[nCursorPos];
+    pThis->m_aInputEvent.mnCursorFlags = 0;
+
+    pThis->m_aInputFlags = std::vector<ExtTextInputAttr>( std::max( 1, static_cast<int>(rText.getLength()) ), ExtTextInputAttr::NONE );
 
     PangoAttrIterator *iter = pango_attr_list_get_iterator(pAttrs);
     do
     {
         GSList *attr_list = nullptr;
         GSList *tmp_list = nullptr;
-        gint start, end;
+        gint nUtf8Start, nUtf8End;
         ExtTextInputAttr sal_attr = ExtTextInputAttr::NONE;
 
-        pango_attr_iterator_range (iter, &start, &end);
-        if (start == G_MAXINT || end == G_MAXINT)
-        {
-            auto len = pText ? g_utf8_strlen(pText, -1) : 0;
-            if (end == G_MAXINT)
-                end = len;
-            if (start == G_MAXINT)
-                start = len;
-        }
-        if (end == start)
+        // docs say... "Get the range of the current segment ... the stored
+        // return values are signed, not unsigned like the values in
+        // PangoAttribute", which implies that the units are otherwise the same
+        // as that of PangoAttribute whose docs state these units are "in
+        // bytes"
+        // so this is the utf8 range
+        pango_attr_iterator_range(iter, &nUtf8Start, &nUtf8End);
+
+        // sanitize the utf8 range
+        nUtf8Start = std::min(nUtf8Start, nUtf8Len);
+        nUtf8End = std::min(nUtf8End, nUtf8Len);
+        if (nUtf8Start >= nUtf8End)
             continue;
 
-        start = g_utf8_pointer_to_offset (pText, pText + start);
-        end = g_utf8_pointer_to_offset (pText, pText + end);
+        // get the utf32 range
+        sal_Int32 nUtf32Start = g_utf8_pointer_to_offset(pText, pText + nUtf8Start);
+        sal_Int32 nUtf32End = g_utf8_pointer_to_offset(pText, pText + nUtf8End);
+
+        // sanitize the utf32 range
+        nUtf32Start = std::min(nUtf32Start, nUtf32Len);
+        nUtf32End = std::min(nUtf32End, nUtf32Len);
+        if (nUtf32Start >= nUtf32End)
+            continue;
 
         tmp_list = attr_list = pango_attr_iterator_get_attrs (iter);
         while (tmp_list)
@@ -4088,11 +4113,12 @@ void GtkSalFrame::IMHandler::signalIMPreeditChanged( GtkIMContext*, gpointer im_
         g_slist_free (attr_list);
 
         // Set the sal attributes on our text
-        for (int i = start; i < end; ++i)
+        // rhbz#1648281 apply over our utf-16 range derived from the input utf-32 range
+        for (sal_Int32 i = aUtf16Offsets[nUtf32Start]; i < aUtf16Offsets[nUtf32End]; ++i)
         {
             SAL_WARN_IF(i >= static_cast<int>(pThis->m_aInputFlags.size()),
                 "vcl.gtk3", "pango attrib out of range. Broken range: "
-                << start << "," << end << " Legal range: 0,"
+                << aUtf16Offsets[nUtf32Start] << "," << aUtf16Offsets[nUtf32End] << " Legal range: 0,"
                 << pThis->m_aInputFlags.size());
             if (i >= static_cast<int>(pThis->m_aInputFlags.size()))
                 continue;
-- 
2.21.0