Implement GetKerningPairs for TrueType fonts

Dmitry Timoshkov dmitry at codeweavers.com
Mon Sep 18 11:12:10 CDT 2006


Hello,

this code produces kerning values which exactly match what Windows returns,
except that our WideCharToMultiByte silently replaces composite unicode
characters by a decomposed main character, while Windows returns DefaultChar
in that case.

Actually I won't notice that if I wasn't writing GetKerningPairsA
implementation under Windows and compared values returned by it to
the ones returned by Wine.

Changelog:
    Implement GetKerningPairs for TrueType fonts.

diff -up cvs/hq/wine/dlls/gdi/font.c wine/dlls/gdi/font.c
--- cvs/hq/wine/dlls/gdi/font.c	2006-08-15 14:51:46.000000000 +0900
+++ wine/dlls/gdi/font.c	2006-09-19 00:52:39.000000000 +0900
@@ -2500,11 +2500,68 @@ BOOL WINAPI CreateScalableFontResourceW(
  *             GetKerningPairsA   (GDI32.@)
  */
 DWORD WINAPI GetKerningPairsA( HDC hDC, DWORD cPairs,
-                               LPKERNINGPAIR lpKerningPairs )
+                               LPKERNINGPAIR kern_pairA )
 {
-    return GetKerningPairsW( hDC, cPairs, lpKerningPairs );
-}
+    INT charset;
+    CHARSETINFO csi;
+    CPINFO cpi;
+    DWORD i, total_kern_pairs, kern_pairs_copied = 0;
+    KERNINGPAIR *kern_pairW;
+
+    if (!cPairs && kern_pairA)
+    {
+        SetLastError(ERROR_INVALID_PARAMETER);
+        return 0;
+    }
+
+    charset = GetTextCharset(hDC);
+    if (!TranslateCharsetInfo((DWORD *)charset, &csi, TCI_SRCCHARSET))
+    {
+        FIXME("Can't find codepage for charset %d\n", charset);
+        return 0;
+    }
+    if (!GetCPInfo(csi.ciACP, &cpi))
+    {
+        FIXME("Can't find codepage %u info\n", csi.ciACP);
+        return 0;
+    }
+    TRACE("charset %d => codepage %u\n", charset, csi.ciACP);
+
+    total_kern_pairs = GetKerningPairsW(hDC, 0, NULL);
+    if (!total_kern_pairs) return 0;
+
+    kern_pairW = HeapAlloc(GetProcessHeap(), 0, total_kern_pairs * sizeof(*kern_pairW));
+    GetKerningPairsW(hDC, total_kern_pairs, kern_pairW);
+
+    for (i = 0; i < total_kern_pairs; i++)
+    {
+        char first, second;
 
+        if (!WideCharToMultiByte(csi.ciACP, 0, &kern_pairW[i].wFirst, 1, &first, 1, NULL, NULL))
+            continue;
+
+        if (!WideCharToMultiByte(csi.ciACP, 0, &kern_pairW[i].wSecond, 1, &second, 1, NULL, NULL))
+            continue;
+
+        if (first == cpi.DefaultChar[0] || second == cpi.DefaultChar[0])
+            continue;
+
+        if (kern_pairA)
+        {
+            if (kern_pairs_copied >= cPairs) break;
+
+            kern_pairA->wFirst = (BYTE)first;
+            kern_pairA->wSecond = (BYTE)second;
+            kern_pairA->iKernAmount = kern_pairW[i].iKernAmount;
+            kern_pairA++;
+        }
+        kern_pairs_copied++;
+    }
+
+    HeapFree(GetProcessHeap(), 0, kern_pairW);
+
+    return kern_pairs_copied;
+}
 
 /*************************************************************************
  *             GetKerningPairsW   (GDI32.@)
@@ -2512,15 +2569,18 @@ DWORD WINAPI GetKerningPairsA( HDC hDC, 
 DWORD WINAPI GetKerningPairsW( HDC hDC, DWORD cPairs,
                                  LPKERNINGPAIR lpKerningPairs )
 {
-    unsigned int i;
-    FIXME("(%p,%ld,%p): almost empty stub!\n", hDC, cPairs, lpKerningPairs);
+    DC *dc = DC_GetDCPtr(hDC);
+    DWORD ret = 0;
 
-    if(!lpKerningPairs) /* return the number of kerning pairs */
-        return 0;
+    TRACE("(%p,%ld,%p)\n", hDC, cPairs, lpKerningPairs);
 
-    for (i = 0; i < cPairs; i++)
-        lpKerningPairs[i].iKernAmount = 0;
-    return 0;
+    if (!dc) return 0;
+
+    if (dc->gdiFont)
+        ret = WineEngGetKerningPairs(dc->gdiFont, cPairs, lpKerningPairs);
+
+    GDI_ReleaseObj(hDC);
+    return ret;
 }
 
 /*************************************************************************
diff -up cvs/hq/wine/dlls/gdi/freetype.c wine/dlls/gdi/freetype.c
--- cvs/hq/wine/dlls/gdi/freetype.c	2006-08-25 13:23:17.000000000 +0900
+++ wine/dlls/gdi/freetype.c	2006-09-19 01:00:00.000000000 +0900
@@ -2,6 +2,7 @@
  * FreeType font engine interface
  *
  * Copyright 2001 Huw D M Davies for CodeWeavers.
+ * Copyright 2006 Dmitry Timoshkov for CodeWeavers.
  *
  * This file contains the WineEng* functions.
  *
@@ -134,6 +135,7 @@ MAKE_FUNCPTR(FT_Vector_Transform);
 static void (*pFT_Library_Version)(FT_Library,FT_Int*,FT_Int*,FT_Int*);
 static FT_Error (*pFT_Load_Sfnt_Table)(FT_Face,FT_ULong,FT_Long,FT_Byte*,FT_ULong*);
 static FT_ULong (*pFT_Get_First_Char)(FT_Face,FT_UInt*);
+static FT_ULong (*pFT_Get_Next_Char)(FT_Face,FT_ULong,FT_UInt*);
 static FT_TrueTypeEngineType (*pFT_Get_TrueType_Engine_Type)(FT_Library);
 #ifdef HAVE_FREETYPE_FTWINFNT_H
 MAKE_FUNCPTR(FT_Get_WinFNT_Header);
@@ -268,6 +270,8 @@ struct tagGdiFont {
     SHORT yMax;
     SHORT yMin;
     OUTLINETEXTMETRICW *potm;
+    DWORD total_kern_pairs;
+    KERNINGPAIR *kern_pairs;
     FONTSIGNATURE fs;
     GdiFont base_font;
     struct list child_fonts;
@@ -1645,6 +1649,7 @@ BOOL WineEngInit(void)
     pFT_Library_Version = wine_dlsym(ft_handle, "FT_Library_Version", NULL, 0);
     pFT_Load_Sfnt_Table = wine_dlsym(ft_handle, "FT_Load_Sfnt_Table", NULL, 0);
     pFT_Get_First_Char = wine_dlsym(ft_handle, "FT_Get_First_Char", NULL, 0);
+    pFT_Get_Next_Char = wine_dlsym(ft_handle, "FT_Get_Next_Char", NULL, 0);
     pFT_Get_TrueType_Engine_Type = wine_dlsym(ft_handle, "FT_Get_TrueType_Engine_Type", NULL, 0);
 #ifdef HAVE_FREETYPE_FTWINFNT_H
     pFT_Get_WinFNT_Header = wine_dlsym(ft_handle, "FT_Get_WinFNT_Header", NULL, 0);
@@ -1931,6 +1936,8 @@ static GdiFont alloc_font(void)
 			ret->gmsize * sizeof(*ret->gm));
     ret->potm = NULL;
     ret->font_desc.matrix.eM11 = ret->font_desc.matrix.eM22 = 1.0;
+    ret->total_kern_pairs = (DWORD)-1;
+    ret->kern_pairs = NULL;
     list_init(&ret->hfontlist);
     list_init(&ret->child_fonts);
     return ret;
@@ -4103,6 +4110,228 @@ BOOL WINAPI GetRasterizerCaps( LPRASTERI
     return TRUE;
 }
 
+/*************************************************************************
+ * Kerning support for TrueType fonts
+ */
+#define MS_KERN_TAG MS_MAKE_TAG('k', 'e', 'r', 'n')
+
+struct TT_kern_table
+{
+    USHORT version;
+    USHORT nTables;
+};
+
+struct TT_kern_subtable
+{
+    USHORT version;
+    USHORT length;
+    union
+    {
+        USHORT word;
+        struct
+        {
+            USHORT horizontal : 1;
+            USHORT minimum : 1;
+            USHORT cross_stream: 1;
+            USHORT override : 1;
+            USHORT reserved1 : 4;
+            USHORT format : 8;
+        } bits;
+    } coverage;
+};
+
+struct TT_format0_kern_subtable
+{
+    USHORT nPairs;
+    USHORT searchRange;
+    USHORT entrySelector;
+    USHORT rangeShift;
+};
+
+struct TT_kern_pair
+{
+    USHORT left;
+    USHORT right;
+    short  value;
+};
+
+static DWORD parse_format0_kern_subtable(GdiFont font, struct TT_format0_kern_subtable *tt_f0_ks,
+                                       USHORT *glyph_to_char,
+                                       KERNINGPAIR *kern_pair, DWORD cPairs)
+{
+    USHORT i, kern_pairs_copied;
+    struct TT_kern_pair *tt_kern_pair;
+
+    TRACE("font height %ld, units_per_EM %d\n", font->ppem, font->ft_face->units_per_EM);
+
+    tt_f0_ks->nPairs = GET_BE_WORD(tt_f0_ks->nPairs);
+    tt_f0_ks->searchRange = GET_BE_WORD(tt_f0_ks->searchRange);
+    tt_f0_ks->entrySelector = GET_BE_WORD(tt_f0_ks->entrySelector);
+    tt_f0_ks->rangeShift = GET_BE_WORD(tt_f0_ks->rangeShift);
+
+    TRACE("nPairs %u, searchRange %u, entrySelector %u, rangeShift %u\n",
+           tt_f0_ks->nPairs, tt_f0_ks->searchRange, tt_f0_ks->entrySelector, tt_f0_ks->rangeShift);
+
+    if (!kern_pair || !cPairs)
+        return tt_f0_ks->nPairs;
+
+    tt_kern_pair = (struct TT_kern_pair *)(tt_f0_ks + 1);
+
+    kern_pairs_copied = 0;
+
+    for (i = 0; i < tt_f0_ks->nPairs; i++)
+    {
+        kern_pair->wFirst = glyph_to_char[GET_BE_WORD(tt_kern_pair[i].left)];
+        kern_pair->wSecond = glyph_to_char[GET_BE_WORD(tt_kern_pair[i].right)];
+        kern_pair->iKernAmount = MulDiv((short)GET_BE_WORD(tt_kern_pair[i].value), font->ppem, font->ft_face->units_per_EM);
+
+        TRACE("left %u right %u value %d\n",
+               kern_pair->wFirst, kern_pair->wSecond, kern_pair->iKernAmount);
+
+        kern_pair++;
+        kern_pairs_copied++;
+        if (!--cPairs) break;
+    }
+    TRACE("copied %u entries\n", kern_pairs_copied);
+    return kern_pairs_copied;
+}
+
+DWORD WineEngGetKerningPairs(GdiFont font, DWORD cPairs, KERNINGPAIR *kern_pair)
+{
+    DWORD length;
+    void *buf;
+    struct TT_kern_table *tt_kern_table;
+    struct TT_kern_subtable *tt_kern_subtable;
+    USHORT i;
+    USHORT *glyph_to_char;
+
+    if (font->total_kern_pairs != (DWORD)-1)
+    {
+        if (cPairs && kern_pair)
+        {
+            cPairs = min(cPairs, font->total_kern_pairs);
+            memcpy(kern_pair, font->kern_pairs, cPairs * sizeof(*kern_pair));
+            return cPairs;
+        }
+        return font->total_kern_pairs;
+    }
+
+    font->total_kern_pairs = 0;
+
+    length = WineEngGetFontData(font, MS_KERN_TAG, 0, NULL, 0);
+
+    if (length == GDI_ERROR)
+    {
+        TRACE("no kerning data in the font\n");
+        return 0;
+    }
+
+    buf = HeapAlloc(GetProcessHeap(), 0, length);
+    if (!buf)
+    {
+        WARN("Out of memory\n");
+        return 0;
+    }
+
+    WineEngGetFontData(font, MS_KERN_TAG, 0, buf, length);
+
+    /* build a glyph index to char code map */
+    glyph_to_char = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(USHORT) * 65536);
+    if (!glyph_to_char)
+    {
+        WARN("Out of memory allocating a glyph index to char code map\n");
+        HeapFree(GetProcessHeap(), 0, buf);
+        return 0;
+    }
+
+    if (font->ft_face->charmap->encoding == FT_ENCODING_UNICODE && pFT_Get_First_Char)
+    {
+        FT_UInt glyph_code;
+        FT_ULong char_code;
+
+        glyph_code = 0;
+        char_code = pFT_Get_First_Char(font->ft_face, &glyph_code);
+
+        TRACE("face encoding FT_ENCODING_UNICODE, number of glyphs %ld, first glyph %u, first char %lu\n",
+               font->ft_face->num_glyphs, glyph_code, char_code);
+
+        while (glyph_code)
+        {
+            /*TRACE("Char %04lX -> Index %u%s\n", char_code, glyph_code, glyph_to_char[glyph_code] ? "  !" : "" );*/
+
+            /* FIXME: This doesn't match what Windows does: it does some fancy
+             * things with duplicate glyph index to char code mappings, while
+             * we just avoid overriding existing entries.
+             */
+            if (glyph_code < 65536 && !glyph_to_char[glyph_code])
+                glyph_to_char[glyph_code] = (USHORT)char_code;
+
+            char_code = pFT_Get_Next_Char(font->ft_face, char_code, &glyph_code);
+        }
+    }
+    else
+    {
+        ULONG n;
+
+        FIXME("encoding %u not supported\n", font->ft_face->charmap->encoding);
+        for (n = 0; n < 65536; n++)
+            glyph_to_char[n] = (USHORT)n;
+    }
+
+    tt_kern_table = buf;
+    tt_kern_table->version = GET_BE_WORD(tt_kern_table->version);
+    tt_kern_table->nTables = GET_BE_WORD(tt_kern_table->nTables);
+    TRACE("version %u, nTables %u\n",
+           tt_kern_table->version, tt_kern_table->nTables);
+
+    tt_kern_subtable = (struct TT_kern_subtable *)(tt_kern_table + 1);
+
+    for (i = 0; i < tt_kern_table->nTables; i++)
+    {
+        tt_kern_subtable->version = GET_BE_WORD(tt_kern_subtable->version);
+        tt_kern_subtable->length = GET_BE_WORD(tt_kern_subtable->length);
+        tt_kern_subtable->coverage.word = GET_BE_WORD(tt_kern_subtable->coverage.word);
+
+        TRACE("version %u, length %u, coverage %u, subtable format %u\n",
+               tt_kern_subtable->version, tt_kern_subtable->length,
+               tt_kern_subtable->coverage.word, tt_kern_subtable->coverage.bits.format);
+
+        /* Accordibg to the TrueType specification this is the only format
+         * that will be properly interpreted by Windows and OS/2
+         */
+        if (tt_kern_subtable->coverage.bits.format == 0)
+        {
+            DWORD new_chunk, old_total = font->total_kern_pairs;
+
+            new_chunk = parse_format0_kern_subtable(font, (struct TT_format0_kern_subtable *)(tt_kern_subtable + 1),
+                                                        glyph_to_char, NULL, 0);
+            font->total_kern_pairs += new_chunk;
+
+            if (!font->kern_pairs)
+                font->kern_pairs = HeapAlloc(GetProcessHeap(), 0,
+                                             font->total_kern_pairs * sizeof(*font->kern_pairs));
+            else
+                font->kern_pairs = HeapReAlloc(GetProcessHeap(), 0, font->kern_pairs,
+                                               font->total_kern_pairs * sizeof(*font->kern_pairs));
+
+            parse_format0_kern_subtable(font, (struct TT_format0_kern_subtable *)(tt_kern_subtable + 1),
+                        glyph_to_char, font->kern_pairs + old_total, new_chunk);
+        }
+
+        tt_kern_subtable = (struct TT_kern_subtable *)((char *)tt_kern_subtable + tt_kern_subtable->length);
+    }
+
+    HeapFree(GetProcessHeap(), 0, glyph_to_char);
+    HeapFree(GetProcessHeap(), 0, buf);
+
+    if (cPairs && kern_pair)
+    {
+        cPairs = min(cPairs, font->total_kern_pairs);
+        memcpy(kern_pair, font->kern_pairs, cPairs * sizeof(*kern_pair));
+        return cPairs;
+    }
+    return font->total_kern_pairs;
+}
 
 #else /* HAVE_FREETYPE */
 
@@ -4240,4 +4469,10 @@ BOOL WINAPI GetRasterizerCaps( LPRASTERI
     return TRUE;
 }
 
+DWORD WineEngGetKerningPairs(GdiFont font, DWORD cPairs, KERNINGPAIR *kern_pair)
+{
+    ERR("called but we don't have FreeType\n");
+    return 0;
+}
+
 #endif /* HAVE_FREETYPE */
diff -up cvs/hq/wine/dlls/gdi/gdi_private.h wine/dlls/gdi/gdi_private.h
--- cvs/hq/wine/dlls/gdi/gdi_private.h	2006-07-13 12:50:14.000000000 +0900
+++ wine/dlls/gdi/gdi_private.h	2006-09-18 22:00:35.000000000 +0900
@@ -376,6 +376,7 @@ extern DWORD WineEngGetGlyphIndices(GdiF
 extern DWORD WineEngGetGlyphOutline(GdiFont, UINT glyph, UINT format,
                                     LPGLYPHMETRICS, DWORD buflen, LPVOID buf,
                                     const MAT2*);
+extern DWORD WineEngGetKerningPairs(GdiFont, DWORD, KERNINGPAIR *);
 extern BOOL WineEngGetLinkedHFont(DC *dc, WCHAR c, HFONT *new_hfont, UINT *glyph);
 extern UINT WineEngGetOutlineTextMetrics(GdiFont, UINT, LPOUTLINETEXTMETRICW);
 extern UINT WineEngGetTextCharsetInfo(GdiFont font, LPFONTSIGNATURE fs, DWORD flags);





More information about the wine-patches mailing list