Qt源码剖析绘制文字原理(Linux)

前言

Qt在Linux绘制字体是使用的FreeType。

FreeType是一个用C语言实现的一个字体光栅化库。它可以用来将字符栅格化并映射成位图以及提供其他字体相关业务的支持。
FreeType并不提供API以运行更高级的功能,如文字布局或图形处理(例如彩色文本渲染、“空洞化”等)。然而,它提供了一个简单、易用并统一的接口来访问字体文件的内容,从而极大地简化了这些任务。它支持各种字体格式,包括TrueType、Type 1、以及OpenType。
FreeType在两个自由软件许可证的许可下发布:GNU通用公共许可证或者以及一个类BSD许可证。因此这个库能够使用于任何类型的项目中,无论其是否是专有软件。同时也包括正在使用的主要自由桌面系统软件。

流程很简单,Qt在绘制字体时,首先将字体中的描述点以及字形信息保存到QPainterPath中。然后再绘制出来。

下面的代码是我从Qt中扣出来了,这是Qt具体描点到Path的过程,Qt在绘制大号字体的时候会调用下面的逻辑

下面上代码

代码

pro文件中加

LIBS += -lfreetype
INCLUDEPATH += /usr/include/freetype2

头文件

#include <freetype2/ft2build.h>
#include <freetype2/freetype/freetype.h>
#include <freetype/freetype.h>
#include <freetype/ftoutln.h>
static void scaleOutline(FT_Face face, FT_GlyphSlot g, FT_Fixed x_scale, FT_Fixed y_scale, QVector<QPoint>& vectors)
{
    x_scale = FT_MulDiv(x_scale, 1 << 10, face->units_per_EM);
    y_scale = FT_MulDiv(y_scale, 1 << 10, face->units_per_EM);
    FT_Vector *p = g->outline.points;
    const FT_Vector *e = p + g->outline.n_points;
    while (p < e) {
        p->x = FT_MulFix(p->x, x_scale);
        p->y = FT_MulFix(p->y, y_scale);
        vectors.push_back(QPoint(p->x, p->y));
        ++p;
    }
}

// 将字形中的
#define GLYPH2PATH_DEBUG QT_NO_QDEBUG_MACRO // qDebug
void addGlyphToPath(FT_Face face, FT_GlyphSlot g, const QFixedPoint &point, QPainterPath *path, FT_Fixed x_scale, FT_Fixed y_scale)
{
    const qreal factor = 1/64.;
    QVector<QPoint> vectors;
    scaleOutline(face, g, x_scale, y_scale, vectors);
    //
    int xMin = 0;
    int xMax = 0;
    foreach (QPoint point, vectors)
    {
        if (point.x() > xMax)
            xMax = point.x();
        if (point.x() < xMin)
            xMin = point.x();
    }
    int glyphWidth = xMax - xMin;
    QPointF cp = point.toPointF();
    // convert the outline to a painter path
    int i = 0;
    for (int j = 0; j < g->outline.n_contours; ++j) {
        int last_point = g->outline.contours[j];
        GLYPH2PATH_DEBUG() << "contour:" << i << "to" << last_point;
        QPointF start = QPointF(g->outline.points[i].x*factor, -g->outline.points[i].y*factor);
        if (!(g->outline.tags[i] & 1)) { // start point is not on curve:
            if (!(g->outline.tags[last_point] & 1)) { // end point is not on curve:
                GLYPH2PATH_DEBUG() << " start and end point are not on curve";
                start = (QPointF(g->outline.points[last_point].x*factor,
                                -g->outline.points[last_point].y*factor) + start) / 2.0;
            } else {
                GLYPH2PATH_DEBUG() << " end point is on curve, start is not";
                start = QPointF(g->outline.points[last_point].x*factor,
                               -g->outline.points[last_point].y*factor);
            }
            --i; // to use original start point as control point below
        }
        start += cp;
        GLYPH2PATH_DEBUG() << " start at" << start;
        path->moveTo(start);
        QPointF c[4];
        c[0] = start;
        int n = 1;
        while (i < last_point) {
            ++i;
            c[n] = cp + QPointF(g->outline.points[i].x*factor, -g->outline.points[i].y*factor);
            GLYPH2PATH_DEBUG() << " " << i << c[n] << "tag =" << (int)g->outline.tags[i]
                               << ": on curve =" << (bool)(g->outline.tags[i] & 1);
            ++n;
            switch (g->outline.tags[i] & 3) {
            case 2:
                // cubic bezier element
                if (n < 4)
                    continue;
                c[3] = (c[3] + c[2])/2;
                --i;
                break;
            case 0:
                // quadratic bezier element
                if (n < 3)
                    continue;
                c[3] = (c[1] + c[2])/2;
                c[2] = (2*c[1] + c[3])/3;
                c[1] = (2*c[1] + c[0])/3;
                --i;
                break;
            case 1:
            case 3:
                if (n == 2) {
                    GLYPH2PATH_DEBUG() << " lineTo" << c[1];
                    path->lineTo(c[1]);
                    c[0] = c[1];
                    n = 1;
                    continue;
                } else if (n == 3) {
                    c[3] = c[2];
                    c[2] = (2*c[1] + c[3])/3;
                    c[1] = (2*c[1] + c[0])/3;
                }
                break;
            }
            GLYPH2PATH_DEBUG() << " cubicTo" << c[1] << c[2] << c[3];
            path->cubicTo(c[1], c[2], c[3]);
            c[0] = c[3];
            n = 1;
        }
        if (n == 1) {
            GLYPH2PATH_DEBUG() << " closeSubpath";
            path->closeSubpath();
        } else {
            c[3] = start;
            if (n == 2) {
                c[2] = (2*c[1] + c[3])/3;
                c[1] = (2*c[1] + c[0])/3;
            }
            GLYPH2PATH_DEBUG() << " close cubicTo" << c[1] << c[2] << c[3];
            path->cubicTo(c[1], c[2], c[3]);
        }
        ++i;
    }
}

///////////////////

    //这里是调用freetype相关的函数

    QPainterPath path;
    FT_Library library;
    FT_Face face;
    FT_Error error = FT_Init_FreeType( &library );
    QString strFileName = QString::fromUtf8("/home/zhangpf/freeType/FZXBSJW_0.TTF");
    std::string strFile = strFileName.toStdString();
    const char* fileName = strFile.c_str();
    error = FT_New_Face(library, fileName, 0, &face );
    uint ch = 'h';
    FT_UInt idxGlyph = FT_Get_Char_Index(face, ch);
    FT_Set_Char_Size(face, face->units_per_EM << 6, face->units_per_EM << 6, 0, 0);

// 这是斜体
//    FT_Matrix matrix;
//    double angle = ( 25.0 / 360 ) * 3.14159 * 2; // 180/12=15 度
//    matrix.xx = (FT_Fixed)(cos(angle) * 0x10000);
//    matrix.xy = (FT_Fixed)(-sin(angle) * 0x10000);
//    matrix.yx = (FT_Fixed)(sin(angle) * 0x10000);
//    matrix.yy = (FT_Fixed)(cos(angle) * 0x10000);
//    FT_Set_Transform(face, &matrix, 0); // 设置或重置旋转
    FT_Error err = FT_Load_Glyph (face, idxGlyph, FT_LOAD_NO_BITMAP);
    FT_BBox bbox = { 0 };
    FT_Outline_Get_CBox(&face->glyph->outline, &bbox);
    /* only oblique outline glyphs */
    if ( face->glyph->format != FT_GLYPH_FORMAT_OUTLINE )
      return;

// 用这个也可以缩放一半
//    FT_Outline* outline = &(face->glyph->outline);
//    FT_Matrix transform;
//    transform.xx = 0x10000L * 0.5;
//    transform.yx = 0x00000L;
//    transform.xy = 0x00000L;
//    transform.yy = 0x10000L;
//    FT_Outline_Transform( outline, &transform );
    QFixedPoint p;
    p.x = 0;
    p.y = 0;
    if (!FT_IS_SCALABLE(face))
    {
        qDebug()<<"123"<<endl;
    }
    else
        addGlyphToPath(face, face->glyph, p, &path, (face->units_per_EM << 6) / 2, face->units_per_EM << 6);  //宽度缩放一半

解释

最后在paintevent中可以绘制出来,由于字形的point中有负数,painter需要transform一下

    QPainter painter(this);
    QTransform transform;
    transform.translate(500,500);
    painter.setTransform(transform);
    painter.drawPath(path);

这是Qt绘制文字的一部分逻辑,我扣出来Demo来给大家学习。主要是使用了QPainterPath来描述字体中的点,以及保存成字体中的贝塞尔曲线。生成QPainterPath来描述具体的字形。代码中我还添加了缩放的变形,实际上字体的绘制变形有很多方法。就拿缩放来讲,就有这么几种

// 用这个也可以缩放一半
FT_Outline* outline = &(face->glyph->outline);
FT_Matrix transform;
transform.xx = 0x10000L * 0.5;
transform.yx = 0x00000L;
transform.xy = 0x00000L;
transform.yy = 0x10000L;
 FT_Outline_Transform( outline, &transform );

// 这是斜体实现,matrix.xx * 0.5 可以缩放 0.5倍
FT_Matrix matrix;
double angle = ( 25.0 / 360 ) * 3.14159 * 2; // 25 度
matrix.xx = (FT_Fixed)(cos(angle) * 0x10000);
matrix.xy = (FT_Fixed)(-sin(angle) * 0x10000);
matrix.yx = (FT_Fixed)(sin(angle) * 0x10000);
matrix.yy = (FT_Fixed)(cos(angle) * 0x10000);
FT_Set_Transform(face, &matrix, 0); // 设置或重置旋转

//甚至可以在 点->path这一步来做   (face->units_per_EM << 6) / 2,
 //addGlyphToPath(face, face->glyph, p, &path, (face->units_per_EM << 6) / 2, face->units_per_EM << 6);  //宽度缩放一半
 (face->units_per_EM << 6) / 2,

所以大部分UI库都有自己的实现方法。这也是freetype灵活的地方.


文章作者: 张小飞
版权声明: 本博客所有文章除特別声明外,均采用 CC BY-NC-ND 4.0 许可协议。转载请注明来源 张小飞 !
 上一篇
Qt在Linux下Wayland桌面上抓取桌面的一个坑 Qt在Linux下Wayland桌面上抓取桌面的一个坑
前言最近WPS在适配了下Wayland的桌面环境,不得不说Gnome下的wayland比KDE的wayland的要稳定的多,然而,还是遇到了一些小坑,这里记录下。 Wayland只是一个协议(Protocol),就像X Window当前的
2020-06-27 张小飞
下一篇 
QFileSystemWatcher无法释放的问题 QFileSystemWatcher无法释放的问题
QFileSystemWatcher 在单例类中,不能指定父类,不能用智能指针,否则会释放不掉,发生资源竞争。要这样释放。 QObject::connect(qApp, SIGNAL(aboutToQuit()), m_pCupsF
2020-06-27 张小飞
  目录