-1

Suppose I write a line of text in a variable-width font to a window with TextOut, and allow the user to click on any letter. Then how do I find out which part of the text he has clicked on? In other words, how do I convert the cursor-coordinates of his click to a string-offset?

I guess it could be done by calling GetTextExtentPoint32 on various string-truncations until I hit the right one, but surely there is a more efficient way. Microsoft's Notepad program knows exactly how many pixels to move when I right-arrow across a line - but how?

Lee H
  • 11
  • What is "Visual C" and why is there no tag for it? Anyway, it is not a matter of the language. – too honest for this site Aug 03 '16 at 13:23
  • 2
    It depends on how you are rendering the text, but that's the basic idea. If there is no kerning, you can have a lookup table for each character width and easily calculate the offset. If your rendering function uses kerning, or justifies text by adding additional spaces, then you need to use the appropriate matching function which will measure it. Notepad usually uses a fixed width font, and I doubt it does any kerning for variable length fonts. – vgru Aug 03 '16 at 13:27
  • Thank you Groo - I get the idea. It seems that TextOut (at least by default) does not kern, so that makes my job relatively easy. It looks like GetCharWidth32() can be used to generate your lookup table (to be confirmed). As for Notepad, oddly enough it does appear to kern (or my Win7 version does). With Times New Roman, "AV" shows a mid-overlap of 1 pixel in font size 12, 2 pixels in font-size 18, 3 in 24, and so on. I don't know what function it uses to measure the offset, or even how it turns on kerning, but I'm quite happy to make do without kerning for my program. – Lee H Aug 04 '16 at 14:34
  • Sorry Olaf, I meant Microsoft Visual Studio 9.0 C, MSDN library. – Lee H Aug 04 '16 at 14:35

1 Answers1

0

This answer was compiled by trial and error after wading through Microsoft's cryptic documentation.

The MSDN C library provides the following functions to display text:

* TextOut, which does not kern
* ExtTextOut, which can kern if its final parameter is non-null
* DrawText, which always kerns

If kerning is required (and on reflection I think that it is desirable) then it is a choice between ExtTextOut and DrawText.

DrawText provides a solution along the lines suggested by Groo. It requires a box to be drawn around the text-area, as in:

void textOut(HDC hdc, int x, int y, char *s, int l)
{
    RECT r = {0};
    r.left = x;
    r.right = Ewidth;
    r.top = y;
    r.bottom = y+LineHt;
    DrawText(hdc,s,l,&r,DT_NOPREFIX);
}

When the fontsize is set or changed, then a character-width lookup-table "CharW" must be built:

ABC CharW[256];     // char-width including leading/trailing space
    GetCharABCWidths(hdc, 0, 0xff, CharW);

When the font is changed, the kerning-table must be built:

KERNINGPAIR *Kern;  // pairs of chars and the (usually negative) additional gap between them
int KernCnt;        // number of same
KERNINGPAIR *CharK[256];// ptr to first kerning-pair for each char

    if(!Kern)
        free(Kern);
    KernCnt = GetKerningPairs(hdc, -1, 0);
    Kern = malloc(KernCnt * sizeof(*Kern));
    GetKerningPairs(hdc, KernCnt, Kern);
    {
        int i;
        for(i = 0; i < KernCnt; ++i) {
            KERNINGPAIR *k = Kern+i;
            if(k->wFirst < 0x100) {
                KERNINGPAIR **k2 = CharK + k->wFirst;
                if(!*k2)
                    *k2 = k;
            }
        }
    }

To play safe, the kerning table "Kern" should be sorted by (wFirst, wSecond), but it appears to be clustered by wFirst and therefore my code works without a qsort.

We can therefore calculate the pixel-width of any substring as follows:

int pixelWidth(char *s, int l)
{
    int x = 0;
    int i;
    for(i = 0; i < l; ++i) {
        char c = s[i];
        ABC *w = CharW+c;
        int wk = 0;
        if(i > 0) {
            char b = s[i-1];
            KERNINGPAIR *k = CharK[b];
            if(k)
            for(; k < Kern+KernCnt  &&  k->wFirst == b; ++k)
            if(k->wSecond == c)
                {wk = k->iKernAmount; break;}
        }
        x += wk + w->abcA + (w->abcB) + w->abcC;
    }
    return x;
}

This has been tested and agrees with the x-coordinate returned by DrawText when the maintain-current-coordinates flag is set:

    SetTextAlign(hdc,TA_UPDATECP)

It is therefore straightfoward to find the substring-length that matches a given pixel-width.

However, a simpler solution is provided by ExtTextOut:

INT W[512];     // maximum string-length

void textOut(HDC hdc, int x, int y, char *s, int l)
{
    GCP_RESULTS g={0};
    g.lStructSize = sizeof(g);
    g.lpDx = W;
    g.nGlyphs = sizeof(W)/sizeof(*W);
    GetCharacterPlacement(hdc, s, l, sizeof(W), &g, GCP_USEKERNING);
    ExtTextOut(hdc, x, y, 0, 0, s, l, g.lpDx);
}

The MSDN function GetCharacterPlacement() returns an array with the actual pixel-width for each character in the string s. It replaces my lookup-tables CharW, Kern, CharK above. According to Microsoft, it has been superseded by Uniscribe functions, though it still works fine for a European language like English.

Lee H
  • 11