Truetype&Harfbuzz&FreeType联合应用完全解析(二)

本章主要简单介绍FreeType,以及结合TrueType来写一个代码示例
你将学习到的知识点有

  1. 什么是FreeType,它和TrueType、OpenTrue有什么联系
  2. 写代码实现使用FreeType读取TrueType字体文件,生成位图数据,然后在屏幕上把位图数据显示出来。笔者使用Qt的窗口来模拟屏幕,实现真正的从字符串数据到可视的字符的效果。效果图如下:
    在这里插入图片描述

1. FreeType介绍

百度百科:FreeType库是一个完全免费(开源)的、高质量的且可移植的字体引擎,它提供统一的接口来访问多种字体格式文件,包括TrueType, OpenType, Type1, CID, CFF, Windows FON/FNT, X11 PCF等。支持单色位图、反走样位图的渲染。

直白地说,freetype就是用来生成字体的位图的一个开源函数库。它可以渲染truetype、opentype、type1等字体文件。truetype在上一篇博客已经介绍过,而opentype是truetype的一个升级版。opentype是Microsoft和Adobe之间竞争与合作的产物,它嵌入了PostScript字体,功能更加强大。

2.TrueType+FreeType生成位图数据

准备材料:

  • truetype字体文件KhmerUI.ttf,这是高棉语的字体文件
  • freetype字体引擎源码,这里使用的是freetype-2.10.1
  • ubuntu16.04,我是虚拟机里面运行的
  • 安装Qt Creator

以上材料的下载链接在这里

ubuntu16.04安装freetype:
一般在ubuntu下,解压并进入源码根目录,直接:

./configure
make 
sudo make install

三部曲,我选择最简单的安装方式。安装过程中可能会出现依赖问题,请自行查找资料解决。

ubuntu16.04安装Qt Creator
使用Qt是为了模拟屏幕来显示渲染出来的字符位图数据,我使用的Qt版本是Qt Creator 3.4.2 (opensource),读者可以去Qt官网上直接下载安装文件。

工作流程
回顾一下前一章的freetype加载字符到渲染出字符的点阵数据的流程

  1. 初始化freetype库
  2. 使用freetype库打开truetype文件(一般是.tty后缀的文件),加载字符的全部数据
  3. 设置字符的大小
  4. 根据要显示的字符设置字符的编码方式,freetype默认使用的是Unicode编码。一般情况是这样的:
    (1)要显示的字符使用utf8格式编码
    (2)使用freetype渲染前,需要将utf8转换Unicode编码,转换算法网上可以搜索到很多
  5. 渲染字符成为需要的点阵数据,然后一个个刷到屏幕上
  6. 字符的显示的位置要根据字符的度量调整,才能正常显示

开始写代码:

  • 目录结构
.
├── contrib                -----第三方库头文件
│   ├── freetype2		-------freetype2库的头文件
├── encoding_conv.c    ----utf8转换成ucs2的函数接口
├── encoding_conv.h
├── font.qrc
├── fonts
│   └── KhmerUI.ttf      -------高棉语truetype文件
├── lib
│   ├── freetype-2.10.1   -------freetype2的共享库文件
├── main.cpp
├── mainwin.cpp
├── mainwin.h
├── qt_font_freetype.pro     ------Qt工程文件
└── qt_font_freetype.pro.user

首先使用Qt Creator创建工程,添加代码和相应的文件

  • 主要代码分析
// mainwin.h
#ifndef MAINWIN_H
#define MAINWIN_H

#include <QWidget>

class MainWin : public QWidget
{
    Q_OBJECT

public:
    MainWin(QWidget *parent = 0);
    ~MainWin();
    quint32 screen_width();
    quint32 screen_height();
    quint8 screen_pixel_mode();
    bool set_simulator_screen_color(quint32 color);

    qint32 draw_text(int x, int y, char *text, unsigned int color, int font_size);

protected:
    void paintEvent(QPaintEvent *event);

private:
   unsigned int* simulator_screen;
   quint32 simulator_screen_width;
   quint32 simulator_screen_height;
   quint8 pixel_mode;
   QPixmap* screen_pixmap;
   // truetype文件的路径,需要根据自己的需要设置
   char* ttf_file = "/home/chen/gui_workspace/qt_font_freetype/fonts/KhmerUI.ttf";
};

#endif // MAINWIN_H
//mainwin构造函数
MainWin::MainWin(QWidget *parent)
    : QWidget(parent)
{
    simulator_screen_width = 500;
    simulator_screen_height = 300;
    //设置窗口大小
    resize(QSize(simulator_screen_width, simulator_screen_height));
    //像素模式使用argb 4个字节
    pixel_mode = 4;
    //此处分配的内存用于模拟显存
    simulator_screen = (unsigned int *)qMallocAligned(simulator_screen_width*simulator_screen_height*pixel_mode, 1);
    
    if (simulator_screen == Q_NULLPTR)
    {
       qDebug("simulator_screen malloc failed\n");
    }
    set_simulator_screen_color(WHITE_COLOR);
    //调用自己实现的draw_text把字符显示到窗口
    draw_text(40, 40, "This is a FreeType test!", RED_COLOR, 27);
}
//draw_text函数实现,此处是本篇最重要的代码
qint32 MainWin::draw_text(int x, int y, char *utf8_text, unsigned int color, int font_size)
{
    int i = 0;
    int j = 0;
    int a = 0;
    int b = 0;
    int k = 0;
    int error;
    int whcar_len;
    int width = 0;
    int m_font_size = font_size;
    wchar_t *ucs2_text;
    int length;
    unsigned int draw_color;
    int pen_x = x;
    int pen_y = y;

    FT_Face FTFace;
    FT_Library library;
    FT_UInt glyphIndex;

    if (utf8_text == NULL)
    {
        return 0;
    }

    if (font_size < 0)
    {
        m_font_size = 18;
    }

    length = strlen(utf8_text) + 1;
    ucs2_text = (wchar_t *)malloc(length * sizeof(ucs2_text));
    if (!ucs2_text)
    {
        return 0;
    }

    utf8_to_ucs2(utf8_text, length, ucs2_text, length);
    whcar_len = wcslen(ucs2_text);

    if (FT_Init_FreeType(&library) != 0)
    {
        qDebug("error func:%s line:%d \r\n",__FUNCTION__,__LINE__);
        return 0;
    }

    if (FT_New_Face(library, ttf_file, 0, &FTFace) != 0)
    {
        qDebug("error func:%s line:%d \r\n",__FUNCTION__,__LINE__);
        return 0;
    }

    FT_Set_Char_Size(FTFace, 0, m_font_size * 64, 96, 96);

    for (k = 0; k < whcar_len; k++)
    {
        glyphIndex = FT_Get_Char_Index(FTFace, ucs2_text[k]);
        error = FT_Load_Glyph(FTFace, glyphIndex, FT_LOAD_DEFAULT);
        if (error)
        {
            qDebug("error func:%s line:%d \r\n",__FUNCTION__,__LINE__);
            continue;
        }
        error = FT_Render_Glyph(FTFace->glyph, ft_render_mode_normal);
        if (error)
        {
            qDebug("error func:%s line:%d \r\n",__FUNCTION__,__LINE__);
            continue;
        }
        FT_GlyphSlot slot = FTFace->glyph;
        FT_Glyph_Metrics metrics = FTFace->glyph->metrics;
        FT_Bitmap bitmap = slot->bitmap;
        qDebug("slot->bitmap_left = %d, slot->bitmap_top = %d, bitmap.rows = %d, bitmap.width = %d\n", slot->bitmap_left, slot->bitmap_top, bitmap.rows, bitmap.width);
        qDebug("metrics.height>>6 = %d, metrics.width>>6 = %d, metrics.vertBearingX>>6 = %d, metrics.vertBearingY>>6 = %d\n", metrics.height>>6, metrics.width>>6, metrics.vertBearingX>>6, metrics.vertBearingY>>6);

        for (a = 0, j = pen_y; j < simulator_screen_height && a < bitmap.rows; j++, a++)
        {
            for (b = 0, i = pen_x; i < simulator_screen_width && b < bitmap.width; i++, b++)
            {
                draw_color = (color & 0x00ffffff) | (bitmap.buffer[ a*bitmap.width + b ] << 24);
                //把字符的点阵数据放到显存中
                simulator_screen[j*simulator_screen_width + i] = draw_color;
            }
        }

        width += slot->advance.x >> 6;
        pen_x += slot->advance.x >> 6; //增加水平方向上的画笔的位置
    }
    qDebug(" %s width = %d\n", __FUNCTION__, width);
    //重新刷新窗口,也就是会自动调用窗口的paintEvent函数,重载了窗口的paintEvent函数,然在里面把显存的数据刷新出来
    this->repaint();
    return width;
}
//重载窗口的paintEvent函数
void MainWin::paintEvent(QPaintEvent *event)
{
    QPainter painter(this);
    QImage image((uchar *)simulator_screen, screen_width(), screen_height(), screen_width()*screen_pixel_mode(), QImage::Format_ARGB32);//data数组 //355宽度 //frame_len 高度//每行字节数//格式
    QPixmap pixmap=QPixmap::fromImage(image);
    painter.drawPixmap(this->rect(), pixmap);
}
  • 显示效果1
    在这里插入图片描述
    由以上显示效果可以看出,字符串是显示出来了,但是好像有些奇怪,垂直方向的字距有问题。原因是垂直方向上没有进行字距调整,freetype渲染出来的位图大小不一定都是相同的,但是freetype有预设了全局的字符串大小,通过FTFace->size可以访问。预设全局大小意味着这是一个合适的大小值,而且全部的字符大小都不会超过它。以下是字距调整的代码:
		//其实只需要再画每一个字符前,调整一下坐标就可以了
        pen_x += slot->bitmap_left;
        pen_y = (FTFace->size->metrics.ascender >> 6) - slot->bitmap_top;

        for (a = 0, j = pen_y; j < simulator_screen_height && a < bitmap.rows; j++, a++)
        {
            for (b = 0, i = pen_x; i < simulator_screen_width && b < bitmap.width; i++, b++)
            {
                draw_color = (color & 0x00ffffff) | (bitmap.buffer[ a*bitmap.width + b ] << 24);
                simulator_screen[j*simulator_screen_width + i] = draw_color;
            }
        }

以下是垂直字距调整后的显示效果:

  • 显示效果2在这里插入图片描述
    对于垂直字距调整的代码:
        pen_x += slot->bitmap_left;
        pen_y = (FTFace->size->metrics.ascender >> 6) - slot->bitmap_top;

为什么这样写?以下是答案:
我们的屏幕坐标轴使用的是如下图所示的坐标轴,Y轴向下增长:

在这里插入图片描述
而freetype渲染出来的位图的坐标是和笛卡尔坐标,即x轴水平向右增长,y轴垂直向上增长。
所以对应于 slot->bitmap_left,slot->bitmap_top,FTFace->size->metrics.ascender就很好理解。如下图所示:

在这里插入图片描述
黑色的矩形框是预设的全局字符大小,是固定的,显示的字符一般不能超过它的大小。但是渲染出来的点阵数据的bitmap_left和bitmap_top不是固定的,所以如果不进行字距调整的话,就会显示地很奇怪。其中FTFace->size->metrics.ascender >> 6,>>6表示这个数是26.6格式数,所以要除以64

总结

  1. truetype font技术描述了字体的各种规则方式,如点和线构成了笔画(Contour),其中曲线使用二阶贝塞尔样条(Bezier-spline)来描述;Contour又组成了最小的描画单位glyph;还有虚拟框EM、坐标空间、转换规则等等。
  2. truptype文件是truetype font技术的具体的实现载体
  3. freetype库是truetype的字体引擎,说白了使用freetype就可以翻译truetype描述的各种规则,然后根据这些规则来获得想要的点阵数据
  4. 笔者搜索了很多网上关于freetype技术的博客,发现几乎都是给出了几行实例的代码,还有一些介绍性的文字,没有让人可以真正得看到一些实际的效果,或者实际的可操作性也不强。本文使用QT的窗口来实现字符的显示效果,能让人从抽象都具体,由浅入深地理解freetype和truetype相关的技术

拓展
freetype一般情况下只能按照给定字符顺序一个个地把字符渲染成点阵数据,但是在某些语言中,如果某两个字符相邻组合在一起可能需要变化成另外一个字符,如"fi"两个相邻组合到一起可能要写成:
在这里插入图片描述
可能语义也会发生改变。这些都是一些特殊的规则,可能这些规则在truetype font中属于一种自定义的规则,因为它是属于某一种语言的,它不是通用的,它是由具体的字体设计厂商规定的。能解决这种问题的一个方法是使用harfbuzz整形引擎,这将在下一篇博客介绍。


他时若遂凌云志, 敢笑黄巢不丈夫

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值