[Libreoffice-commits] core.git: sd/qa vcl/inc vcl/quartz vcl/source vcl/unx vcl/win

Khaled Hosny khaledhosny at eglug.org
Tue Nov 22 15:32:33 UTC 2016


 sd/qa/unit/tiledrendering/tiledrendering.cxx   |    1 
 vcl/inc/impfontmetricdata.hxx                  |    4 +
 vcl/inc/sft.hxx                                |   15 +++++
 vcl/quartz/ctfonts.cxx                         |   22 +++++--
 vcl/source/font/fontmetric.cxx                 |   72 +++++++++++++++++++++++++
 vcl/source/fontsubset/sft.cxx                  |   29 ++++++++++
 vcl/unx/generic/glyphs/freetype_glyphcache.cxx |   68 +++--------------------
 vcl/win/gdi/salfont.cxx                        |   42 ++++----------
 8 files changed, 160 insertions(+), 93 deletions(-)

New commits:
commit 34d7602954d4483b3bc9db700e7df2c15348947a
Author: Khaled Hosny <khaledhosny at eglug.org>
Date:   Wed Nov 9 15:22:43 2016 +0200

    tdf#55469 Consistent line spacing across platforms
    
    We current use platform APIs to calculate line spacing, however
    different platforms behave differently:
    
    * FreeType and Core Text will prefer hhea table over OS/2, and OS/2 Typo
      metrics over Win ones.
    * GDI’s TEXTMETRIC only uses OS/2 Win metrics, while NEWTEXTMETRIC seems
      to use Typo one, but we use only the old TEXTMETRIC.
    
    So we get inconsistent line spacing and we have no control which of
    three competing sets of line spacing metrics we end up using.
    
    The current conventional wisdom is that:
    * hhea metrics should be used, since hhea is a mandatory font table and
      should always be present.
    * But if OS/2 is present, it should be used since it is mandatory in
      Windows.
      OS/2 has Typo and Win metrics, but the later was meant to control
      text clipping not line spacing and can be ridiculously large.
      Unfortunately many Windows application incorrectly use the Win metrics
      (thanks to GDI’s TEXTMETRIC) and old fonts might be designed with this
      in mind, so OpenType introduced a flag for fonts to indicate that they
      really want to use Typo metrics. So for best backward compatibility:
      * Use Win metrics if available.
      * Unless USE_TYPO_METRICS flag is set, in which case use Typo metrics.
    
    This patch does this by reading the hhea and OS/2 tables directly and
    implementing the algorithm above.
    
    Quick comparison with Microsoft Office 2016 shows similar line spacing
    as the new line spacing here, so I guess we are improving compatibility
    as well.
    
    Change-Id: I4541e67e3e14508e3529e73083056a09de02e637
    Reviewed-on: https://gerrit.libreoffice.org/31053
    Tested-by: Jenkins <ci at libreoffice.org>
    Reviewed-by: Khaled Hosny <khaledhosny at eglug.org>

diff --git a/sd/qa/unit/tiledrendering/tiledrendering.cxx b/sd/qa/unit/tiledrendering/tiledrendering.cxx
index 2d2cc99..c07f00e 100644
--- a/sd/qa/unit/tiledrendering/tiledrendering.cxx
+++ b/sd/qa/unit/tiledrendering/tiledrendering.cxx
@@ -353,6 +353,7 @@ void SdTiledRenderingTest::testPostMouseEvent()
     vcl::Cursor* pCursor = rEditView.GetCursor();
     Point aPosition = pCursor->GetPos();
     aPosition.setX(aPosition.getX() - 1000);
+    aPosition.setY(aPosition.getY() + 1000);
     pXImpressDocument->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONDOWN,
                                       convertMm100ToTwip(aPosition.getX()), convertMm100ToTwip(aPosition.getY()),
                                       1, MOUSE_LEFT, 0);
diff --git a/vcl/inc/impfontmetricdata.hxx b/vcl/inc/impfontmetricdata.hxx
index 7251778..8122bbd 100644
--- a/vcl/inc/impfontmetricdata.hxx
+++ b/vcl/inc/impfontmetricdata.hxx
@@ -98,9 +98,13 @@ public:
 
     void            ImplInitTextLineSize( const OutputDevice* pDev );
     void            ImplInitAboveTextLineSize();
+    void            ImplCalcLineSpacing(const std::vector<uint8_t>& rHhea,
+                                        const std::vector<uint8_t>& rOS_2,
+                                        int nUPEM);
 
 private:
     // font instance attributes from the font request
+    long            mnHeight;                   // Font size
     long            mnWidth;                    // Reference Width
     short           mnOrientation;              // Rotation in 1/10 degrees
 
diff --git a/vcl/inc/sft.hxx b/vcl/inc/sft.hxx
index 04aaf14..5f65488 100644
--- a/vcl/inc/sft.hxx
+++ b/vcl/inc/sft.hxx
@@ -204,6 +204,7 @@ namespace vcl
         sal_uInt32 ur4;           /**< bits 96 - 127 of Unicode Range flags                    */
         sal_uInt8  panose[10];    /**< PANOSE classification number                            */
         sal_uInt32 typeFlags;     /**< type flags (copyright bits + PS-OpenType flag)       */
+        sal_uInt16 fsSelection;   /**< OS/2 fsSelection */
     } TTGlobalFontInfo;
 
 #define TYPEFLAG_INVALID        0x8000000
@@ -508,6 +509,20 @@ namespace vcl
     void GetTTGlobalFontInfo(TrueTypeFont *ttf, TTGlobalFontInfo *info);
 
 /**
+ * Returns fonts metrics.
+ * @see TTGlobalFontInfo
+ *
+ * @param hhea        hhea table data
+ * @param os2         OS/2 table data
+ * @param info        pointer to a TTGlobalFontInfo structure
+ * @ingroup sft
+ *
+ */
+ void GetTTFontMterics(const std::vector<uint8_t>& hhea,
+                       const std::vector<uint8_t>& os2,
+                       TTGlobalFontInfo *info);
+
+/**
  * returns the number of glyphs in a font
  */
  int GetTTGlyphCount( TrueTypeFont* ttf );
diff --git a/vcl/quartz/ctfonts.cxx b/vcl/quartz/ctfonts.cxx
index c111613..20f5414 100644
--- a/vcl/quartz/ctfonts.cxx
+++ b/vcl/quartz/ctfonts.cxx
@@ -127,12 +127,22 @@ void CoreTextStyle::GetFontMetric( ImplFontMetricDataRef& rxFontMetric ) const
     // TODO: is it worth it to cache the CTFontRef in SetFont() and reuse it here?
     CTFontRef aCTFontRef = static_cast<CTFontRef>(CFDictionaryGetValue( mpStyleDict, kCTFontAttributeName ));
 
-    const CGFloat fAscent = CTFontGetAscent( aCTFontRef );
-    const CGFloat fCapHeight = CTFontGetCapHeight( aCTFontRef );
-    rxFontMetric->SetAscent( lrint( fAscent ) );
-    rxFontMetric->SetDescent( lrint( CTFontGetDescent( aCTFontRef )) );
-    rxFontMetric->SetExternalLeading( lrint( CTFontGetLeading( aCTFontRef )) );
-    rxFontMetric->SetInternalLeading( lrint( fAscent - fCapHeight ) );
+    int nBufSize = 0;
+
+    nBufSize = mpFontData->GetFontTable("hhea", nullptr);
+    uint8_t* pHheaBuf = new uint8_t[nBufSize];
+    nBufSize = mpFontData->GetFontTable("hhea", pHheaBuf);
+    std::vector<uint8_t> rHhea(pHheaBuf, pHheaBuf + nBufSize);
+
+    nBufSize = mpFontData->GetFontTable("OS/2", nullptr);
+    uint8_t* pOS2Buf = new uint8_t[nBufSize];
+    nBufSize = mpFontData->GetFontTable("OS/2", pOS2Buf);
+    std::vector<uint8_t> rOS2(pOS2Buf, pOS2Buf + nBufSize);
+
+    rxFontMetric->ImplCalcLineSpacing(rHhea, rOS2, CTFontGetUnitsPerEm(aCTFontRef));
+
+    delete[] pHheaBuf;
+    delete[] pOS2Buf;
 
     // since ImplFontMetricData::mnWidth is only used for stretching/squeezing fonts
     // setting this width to the pixel height of the fontsize is good enough
diff --git a/vcl/source/font/fontmetric.cxx b/vcl/source/font/fontmetric.cxx
index 9cde929..3d7bf9a 100644
--- a/vcl/source/font/fontmetric.cxx
+++ b/vcl/source/font/fontmetric.cxx
@@ -24,6 +24,7 @@
 #include "impfontmetric.hxx"
 #include "impfontmetricdata.hxx"
 #include "PhysicalFontFace.hxx"
+#include "sft.hxx"
 
 #include <vector>
 #include <set>
@@ -214,6 +215,7 @@ bool ImplFontMetric::operator==( const ImplFontMetric& r ) const
 
 ImplFontMetricData::ImplFontMetricData( const FontSelectPattern& rFontSelData )
     : FontAttributes( rFontSelData )
+    , mnHeight ( rFontSelData.mnHeight )
     , mnWidth ( rFontSelData.mnWidth )
     , mnOrientation( (short)(rFontSelData.mnOrientation) )
     , mnAscent( 0 )
@@ -424,4 +426,74 @@ void ImplFontMetricData::ImplInitAboveTextLineSize()
     mnAboveWUnderlineOffset = nCeiling + (nIntLeading + 1) / 2;
 }
 
+/*
+ * Calculate line spacing:
+ *
+ * - hhea metrics should be used, since hhea is a mandatory font table and
+ *   should always be present.
+ * - But if OS/2 is present, it should be used since it is mandatory in
+ *   Windows.
+ *   OS/2 has Typo and Win metrics, but the later was meant to control
+ *   text clipping not line spacing and can be ridiculously large.
+ *   Unfortunately many Windows application incorrectly use the Win metrics
+ *   (thanks to GDI’s TEXTMETRIC) and old fonts might be designed with this
+ *   in mind, so OpenType introduced a flag for fonts to indicate that they
+ *   really want to use Typo metrics. So for best backward compatibility:
+ *   - Use Win metrics if available.
+ *   - Unless USE_TYPO_METRICS flag is set, in which case use Typo metrics.
+*/
+void ImplFontMetricData::ImplCalcLineSpacing(const std::vector<uint8_t>& rHheaData,
+        const std::vector<uint8_t>& rOS2Data, int nUPEM)
+{
+    mnAscent = mnDescent = mnExtLeading = mnIntLeading = 0;
+
+    double fScale = static_cast<double>(mnHeight) / nUPEM;
+
+    vcl::TTGlobalFontInfo rInfo;
+    memset(&rInfo, 0, sizeof(vcl::TTGlobalFontInfo));
+    GetTTFontMterics(rHheaData, rOS2Data, &rInfo);
+
+    // Try hhea table first.
+    if (rInfo.ascender || rInfo.descender)
+    {
+        mnAscent     =  rInfo.ascender  * fScale;
+        mnDescent    = -rInfo.descender * fScale;
+        mnExtLeading =  rInfo.linegap   * fScale;
+    }
+
+    // But if OS/2 is present, prefer it.
+    if (rInfo.winAscent || rInfo.winDescent || rInfo.typoAscender || rInfo.typoDescender)
+    {
+        if (mnAscent == 0 && mnDescent == 0)
+        {
+            mnAscent     = rInfo.winAscent  * fScale;
+            mnDescent    = rInfo.winDescent * fScale;
+            mnExtLeading = 0;
+        }
+
+        const uint16_t kUseTypoMetricsMask = 1 << 7;
+        if (rInfo.fsSelection & kUseTypoMetricsMask)
+        {
+            mnAscent     =  rInfo.typoAscender  * fScale;
+            mnDescent    = -rInfo.typoDescender * fScale;
+            mnExtLeading =  rInfo.typoLineGap   * fScale;
+        }
+    }
+
+    if (mnAscent || mnDescent)
+        mnIntLeading = mnAscent + mnDescent - mnHeight;
+
+    SAL_INFO("vcl.gdi.fontmetric",
+                  "fsSelection: "   << rInfo.fsSelection
+             << ", typoAscender: "  << rInfo.typoAscender
+             << ", typoDescender: " << rInfo.typoDescender
+             << ", typoLineGap: "   << rInfo.typoLineGap
+             << ", winAscent: "     << rInfo.winAscent
+             << ", winDescent: "    << rInfo.winDescent
+             << ", ascender: "      << rInfo.ascender
+             << ", descender: "     << rInfo.descender
+             << ", linegap: "       << rInfo.linegap
+             );
+}
+
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/fontsubset/sft.cxx b/vcl/source/fontsubset/sft.cxx
index 177766e..d4a8b5a 100644
--- a/vcl/source/fontsubset/sft.cxx
+++ b/vcl/source/fontsubset/sft.cxx
@@ -2520,6 +2520,35 @@ TTSimpleGlyphMetrics *GetTTSimpleCharMetrics(TrueTypeFont * ttf, sal_uInt16 firs
     return res;
 }
 
+// TODO, clean up table parsing and re-use it elsewhere in this file.
+void GetTTFontMterics(const std::vector<uint8_t>& hhea,
+                      const std::vector<uint8_t>& os2,
+                      TTGlobalFontInfo *info)
+{
+    /* There are 3 different versions of OS/2 table: original (68 bytes long),
+     * Microsoft old (78 bytes long) and Microsoft new (86 bytes long,)
+     * Apple's documentation recommends looking at the table length.
+     *
+     * FIXME: horribly outdated comment and horrible code that uses hard-coded
+     * offsets to read the table.
+     */
+    if (os2.size() > 76 + 2)
+    {
+        info->fsSelection   = GetUInt16(os2.data(), 62);
+        info->typoAscender  = GetInt16(os2.data(),  68);
+        info->typoDescender = GetInt16(os2.data(),  70);
+        info->typoLineGap   = GetInt16(os2.data(),  72);
+        info->winAscent     = GetUInt16(os2.data(), 74);
+        info->winDescent    = GetUInt16(os2.data(), 76);
+    }
+
+    if (hhea.size() > 8 + 2) {
+        info->ascender      = GetInt16(hhea.data(), 4);
+        info->descender     = GetInt16(hhea.data(), 6);
+        info->linegap       = GetInt16(hhea.data(), 8);
+    }
+}
+
 void GetTTGlobalFontInfo(TrueTypeFont *ttf, TTGlobalFontInfo *info)
 {
     int UPEm = ttf->unitsPerEm;
diff --git a/vcl/unx/generic/glyphs/freetype_glyphcache.cxx b/vcl/unx/generic/glyphs/freetype_glyphcache.cxx
index e398257..2102e4a 100644
--- a/vcl/unx/generic/glyphs/freetype_glyphcache.cxx
+++ b/vcl/unx/generic/glyphs/freetype_glyphcache.cxx
@@ -637,68 +637,20 @@ void FreetypeFont::GetFontMetric( ImplFontMetricDataRef& rxTo, long& rFactor ) c
 
     rFactor = 0x100;
 
-    const TT_OS2* pOS2 = static_cast<const TT_OS2*>(FT_Get_Sfnt_Table( maFaceFT, ft_sfnt_os2 ));
-    const double fScale = (double)GetFontSelData().mnHeight / maFaceFT->units_per_EM;
+    sal_uLong nHhea = 0;
+    const uint8_t* pHheaBuf = mpFontInfo->GetTable("hhea", &nHhea);
+    const std::vector<uint8_t> rHhea(pHheaBuf, pHheaBuf + nHhea);
 
-    rxTo->SetAscent( 0 );
-    rxTo->SetDescent( 0 );
-    rxTo->SetExternalLeading( 0 );
-    rxTo->SetSlant( 0 );
-    rxTo->SetWidth( mnWidth );
+    sal_uLong nOS2 = 0;
+    const uint8_t* pOS2Buf = mpFontInfo->GetTable("OS/2", &nOS2);
+    const std::vector<uint8_t> rOS2(pOS2Buf, pOS2Buf + nOS2);
 
-    // Calculating ascender and descender:
-    // FreeType >= 2.4.6 does the right thing, so we just use what it gives us,
-    // for earlier versions we emulate its behaviour;
-    // take them from 'hhea' table,
-    // if zero take them from 'OS/2' table,
-    // if zero take them from FreeType's font metrics
-    if (nFTVERSION >= 2406)
-    {
-        const FT_Size_Metrics& rMetrics = maFaceFT->size->metrics;
-        rxTo->SetAscent( (rMetrics.ascender + 32) >> 6 );
-        rxTo->SetDescent( (-rMetrics.descender + 32) >> 6 );
-        rxTo->SetExternalLeading( ((rMetrics.height + 32) >> 6) - (rxTo->GetAscent() + rxTo->GetDescent()) );
-    }
-    else
-    {
-        const TT_HoriHeader* pHHea = static_cast<const TT_HoriHeader*>(FT_Get_Sfnt_Table(maFaceFT, ft_sfnt_hhea));
-        if (pHHea)
-        {
-            rxTo->SetAscent( pHHea->Ascender * fScale + 0.5 );
-            rxTo->SetDescent( -pHHea->Descender * fScale + 0.5 );
-            rxTo->SetExternalLeading( pHHea->Line_Gap * fScale + 0.5 );
-        }
+    rxTo->ImplCalcLineSpacing(rHhea, rOS2, maFaceFT->units_per_EM);
 
-        if (!(rxTo->GetAscent() || rxTo->GetDescent()))
-        {
-            if (pOS2 && (pOS2->version != 0xFFFF))
-            {
-                if (pOS2->sTypoAscender || pOS2->sTypoDescender)
-                {
-                    rxTo->SetAscent( pOS2->sTypoAscender * fScale + 0.5 );
-                    rxTo->SetDescent( -pOS2->sTypoDescender * fScale + 0.5 );
-                    rxTo->SetExternalLeading( pOS2->sTypoLineGap * fScale + 0.5 );
-                }
-                else
-                {
-                    rxTo->SetAscent( pOS2->usWinAscent * fScale + 0.5 );
-                    rxTo->SetDescent( pOS2->usWinDescent * fScale + 0.5 );
-                    rxTo->SetExternalLeading( 0 );
-                }
-            }
-        }
-
-        if (!(rxTo->GetAscent() || rxTo->GetDescent()))
-        {
-            const FT_Size_Metrics& rMetrics = maFaceFT->size->metrics;
-            rxTo->SetAscent( (rMetrics.ascender + 32) >> 6 );
-            rxTo->SetDescent( (-rMetrics.descender + 32) >> 6 );
-            rxTo->SetExternalLeading( ((rMetrics.height + 32) >> 6) - (rxTo->GetAscent() + rxTo->GetDescent() ) );
-        }
-    }
-
-    rxTo->SetInternalLeading( rxTo->GetAscent() + rxTo->GetDescent() - (maFaceFT->units_per_EM * fScale + 0.5) );
+    rxTo->SetSlant( 0 );
+    rxTo->SetWidth( mnWidth );
 
+    const TT_OS2* pOS2 = static_cast<const TT_OS2*>(FT_Get_Sfnt_Table( maFaceFT, ft_sfnt_os2 ));
     if( pOS2 && (pOS2->version != 0xFFFF) )
     {
         // map the panose info from the OS2 table to their VCL counterparts
diff --git a/vcl/win/gdi/salfont.cxx b/vcl/win/gdi/salfont.cxx
index d2e11f8..d2d964d 100644
--- a/vcl/win/gdi/salfont.cxx
+++ b/vcl/win/gdi/salfont.cxx
@@ -1245,14 +1245,21 @@ void WinSalGraphics::GetFontMetric( ImplFontMetricDataRef& rxFontMetric, int nFa
     if( ::GetTextFaceW( getHDC(), sizeof(aFaceName)/sizeof(wchar_t), aFaceName ) )
         rxFontMetric->SetFamilyName(OUString(reinterpret_cast<const sal_Unicode*>(aFaceName)));
 
+    const DWORD nHheaTag = CalcTag("hhea");
+    const DWORD nOS2Tag = CalcTag("OS/2");
+    const RawFontData aHheaRawData(getHDC(), nHheaTag);
+    const RawFontData aOS2RawData(getHDC(), nOS2Tag);
+
     // get the font metric
-    TEXTMETRICA aWinMetric;
-    const bool bOK = GetTextMetricsA( getHDC(), &aWinMetric );
+    OUTLINETEXTMETRICW aOutlineMetric;
+    const bool bOK = GetOutlineTextMetricsW(getHDC(), sizeof(OUTLINETEXTMETRICW), &aOutlineMetric);
     // restore the HDC to the font in the base level
     SelectFont( getHDC(), hOldFont );
     if( !bOK )
         return;
 
+    TEXTMETRICW aWinMetric = aOutlineMetric.otmTextMetrics;
+
     // device independent font attributes
     rxFontMetric->SetFamilyType(ImplFamilyToSal( aWinMetric.tmPitchAndFamily ));
     rxFontMetric->SetSymbolFlag(aWinMetric.tmCharSet == SYMBOL_CHARSET);
@@ -1282,33 +1289,10 @@ void WinSalGraphics::GetFontMetric( ImplFontMetricDataRef& rxFontMetric, int nFa
 
     // transformation dependent font metrics
     rxFontMetric->SetWidth( static_cast<int>( mfFontScale[nFallbackLevel] * aWinMetric.tmAveCharWidth ) );
-    rxFontMetric->SetInternalLeading( static_cast<int>( mfFontScale[nFallbackLevel] * aWinMetric.tmInternalLeading ) );
-    rxFontMetric->SetExternalLeading( static_cast<int>( mfFontScale[nFallbackLevel] * aWinMetric.tmExternalLeading ) );
-    rxFontMetric->SetAscent( static_cast<int>( mfFontScale[nFallbackLevel] * aWinMetric.tmAscent ) );
-    rxFontMetric->SetDescent( static_cast<int>( mfFontScale[nFallbackLevel] * aWinMetric.tmDescent ) );
-
-    // #107888# improved metric compatibility for Asian fonts...
-    // TODO: assess workaround below for CWS >= extleading
-    // TODO: evaluate use of aWinMetric.sTypo* members for CJK
-    if( mpWinFontData[nFallbackLevel] && mpWinFontData[nFallbackLevel]->SupportsCJK() )
-    {
-        rxFontMetric->SetInternalLeading( rxFontMetric->GetInternalLeading() + rxFontMetric->GetExternalLeading() );
-
-        // #109280# The line height for Asian fonts is too small.
-        // Therefore we add half of the external leading to the
-        // ascent, the other half is added to the descent.
-        const long nHalfTmpExtLeading = rxFontMetric->GetExternalLeading() / 2;
-        const long nOtherHalfTmpExtLeading = rxFontMetric->GetExternalLeading() - nHalfTmpExtLeading;
-
-        // #110641# external leading for Asian fonts.
-        // The factor 0.3 has been confirmed with experiments.
-        long nCJKExtLeading = static_cast<long>(0.30 * (rxFontMetric->GetAscent() + rxFontMetric->GetDescent()));
-        nCJKExtLeading -= rxFontMetric->GetExternalLeading();
-        rxFontMetric->SetExternalLeading( (nCJKExtLeading > 0) ? nCJKExtLeading : 0 );
-
-        rxFontMetric->SetAscent( rxFontMetric->GetAscent() + nHalfTmpExtLeading );
-        rxFontMetric->SetDescent(  rxFontMetric->GetDescent() + nOtherHalfTmpExtLeading );
-    }
+
+    const std::vector<uint8_t> rHhea(aHheaRawData.get(), aHheaRawData.get() + aHheaRawData.size());
+    const std::vector<uint8_t> rOS2(aOS2RawData.get(), aOS2RawData.get() + aOS2RawData.size());
+    rxFontMetric->ImplCalcLineSpacing(rHhea, rOS2, aOutlineMetric.otmEMSquare);
 
     rxFontMetric->SetMinKashida( GetMinKashidaWidth() );
 }


More information about the Libreoffice-commits mailing list