如何在Visual C中将光标位置转换为文本位置?

时间:2016-08-03 13:21:15

标签: c textout

假设我用可变宽度字体将一行文本写入带有TextOut的窗口,并允许用户单击任何字母。然后我如何找出他点击的文本的哪一部分?换句话说,如何将其点击的光标坐标转换为字符串偏移?

我想这可以通过在各种字符串截断上调用GetTextExtentPoint32来完成,直到我找到正确的方法,但肯定有更有效的方法。微软的记事本程序确切地知道当我向右箭头划线时要移动的像素数 - 但是如何?

1 个答案:

答案 0 :(得分:0)

这个答案是在浏览了微软的神秘文档之后通过反复试验编写的。

MSDN C库提供以下功能来显示文本:

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

如果需要字距调整(并且反思我认为这是可取的)那么它是ExtTextOut和DrawText之间的选择。

DrawText提供了Groo建议的解决方案。这个需要 要在文本区域周围绘制的框,如:

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);
}

设置或更改fontsize时,必须构建字符宽度查找表“CharW”:

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

更改字体时,必须构建字距调整表:

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;
            }
        }
    }

为了安全起见,字距调整表“Kern”应按(wFirst,wSecond)排序,但它似乎是由wFirst聚类的,因此我的代码在没有qsort的情况下工作。

因此我们可以按如下方式计算任何子串的像素宽度:

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;
}

当设置了maintain-current-coordinates标志时,这已经过测试并与DrawText返回的x坐标一致:

    SetTextAlign(hdc,TA_UPDATECP)

因此,很容易找到与给定像素宽度匹配的子串长度。

然而,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);
}

MSDN函数GetCharacterPlacement()返回一个数组,其中包含字符串s中每个字符的实际像素宽度。它取代了我的查找表CharW,Kern,CharK。根据微软的说法,它已经被Uniscribe功能所取代,尽管它仍适用于像英语这样的欧洲语言。