FreeTpye库学习笔记:将矢量字体解析为位图

技术杂谈50

近期工作需要研究 FreeType 库,因此本篇文章记录一下该库的基本用法。

FreeType 是一个免费、开源、可移植且高质量的字体引擎,它有以下优点:

但 FreeType 也有缺点,它太大了,即使是裁剪后的阉割版,其代码量也有将近 12 万行,在Flash非常小的开发板可能放不下,此时可以考虑用代码量较小的 stb_truetype 库,具体用法可以参考我之前发的博客:stb_truetype解析ttf字体并将其保存到图片中。

注意:本文采用的 FreeType 库为 AWTK 中的裁剪版,只有一个巨大的头文件,下载链接:https://github.com/zlgopen/awtk/tree/master/3rd/freetype_single_header。

AWTK是 ZLG 开发的开源 GUI 引擎,官网地址:https://www.zlg.cn/index/pub/awtk.html。

二、FreeType解析矢量(.ttf)字体

使用 FreeType 库将矢量(.ttf)字体解析为位图的步骤详见下文。

此处采用裁剪版的 FreeType 库,需要包含的头文件如下:

#include "freetype.h"

如果采用完整版的 FreeType 库,需要包含的头文件如下:

#include
#include
#include
#include
#include

使用 FT_Init_FreeType() 函数初始化一个 FT_Library 句柄,例如:

FT_LIBRARY library;                         //FreeType库的句柄
FT_Error error = FT_Init_FreeType( &library );
if ( error )
{  /* 初始化失败 */  }

FT_Face 是矢量字体的外观对象,用于保存字体外观数、当前外观索引、当前外观所包含字形文件数等相关数据,其中外观可简单理解为常规、斜体、加粗等字体风格,加载 FT_Face 有两种方式,一是通过字体文件名加载,二是从内存中加载,具体如下:

1、通过字体文件名加载FT_Face:

FT_Face face;                        //FT_Face对象的句柄
FT_New_Face( library, "/usr/share/fonts/truetype/arial.ttf", 0, &face);

2、从内存中加载 FT_Face:

FT_Face face;                        //FT_Face对象的句柄
FT_New_Memory_Face(font_info.library, font_buf, size, 0, &face);

此处设置字体编码方式为Unicode,如果不设置,FreeType默认为 utf-16编码类型:

FT_Select_Charmap(face, FT_ENCODING_UNICODE);

方法一:通过物理距离设置字体大小

FT_Set_Char_Size( FT_Face     face,
                  FT_F26Dot6  char_width,        //字符宽度,单位为1/64点
                  FT_F26Dot6  char_height,       //字符高度,单位为1/64点
                  FT_UInt     horz_resolution,   //水平分辨率
                  FT_UInt     vert_resolution ); //垂直分辨率

字符宽度和高度以1/64点为单位表示。

点是物理上的距离,一个点代表1/72英寸(2.54cm)

分辨率以dpi(dots per inch)为单位表示,表示一个英寸有多少个像素 例如:

FT_Set_Char_Size( face, 36 * 64, 0, 96 , 0);    //0表示与另一个尺寸值相等。

字符的物理大小:36 * 64 * (1/64) * (1/72) 英寸

字符的像素宽高: 36 * 64 * (1/64) * (1/72) * 96

方法二:通过像素宽高设置字体大小

FT_Set_Pixel_Sizes( FT_Face  face,
                    FT_UInt  pixel_width,     //像素宽度
                    FT_UInt  pixel_height );  //像素高低

例如:

FT_Set_Pixel_Sizes(face, 0 ,16);      //把字符像素设置为16*16像素, 0表示与另一个尺寸值相等。

通过 FT_Get_Char_Index() 函数将字符编码转换为一个字形(glyph)索引,例如:

wchar_t char_code = L'A';
FT_UInt glyph_index = FT_Get_Char_Index(face, char_code);     /* 若 glyph_index 为0,表示没找到字形索引 */

获得字形索引后,接下来可根据索引将字形图像存储到字形槽( FT_GlyphSlot )中。

字形槽:每次只能存储一个字形图像,每个face对象都有一个字形槽,位于face->glyph。

通过 FT_Load_Glyph() 来加载一个字形图像到字形槽:

FT_Load_Glyph(face, glyph_index, load_flags); /* load_flags:装载标志,一般填FT_LOAD_DEFAULT */

并且更新 face->glyph 下的其它成员,比如:

FT_Int            bitmap_left;            //该字形图像的最左边的X值
FT_Int            bitmap_top;             //该字形图像的最上边的Y值

通过 FT_Render_Glyph() 函数,将字形槽的字形图像转为位图,并存到 face->glyph->bitmap->buffer[]里:

FT_Render_Glyph(face->glyph, render_mode); /* render_mode:渲染模式 */

渲染模式 render_mode 通常设为以下2种:

  • FT_RENDER_MODE_NORMAL:表示生成位图是RGB888格式
  • FT_RENDER_MODE_MONO:表示生成位图每个像素是1位的(黑白图)

并更新face->glyph->bitmap下的其它成员,比如:

int             rows;         //该位图总高度,有多少行
int             width;        //该位图总宽度,有多少列像素点
int             pitch:        //指一行的数据跨度(字节数),比如对于24位(3字节)的24*30汉字,则pitch=24*3
char            pixel_mode    //像素模式,1 指单色的,8 表示反走样灰度值
unsigned char*  buffer        //glyph 的点阵位图内存绶冲区

也可以直接使用 FT_Load_Char() 来代替 FT_Get_Char_Index()、FT_Get_Load_Glyph() 和 FT_Render_Glyph(),一步到位,例如:

FT_Load_Char(face, char_code, FT_LOAD_RENDER);

其中 FT_LOAD_RENDER 表示直接将图像转为位图,所以不需要使用 FT_Render_Glyph() 函数。

该函数默认生成的位图是 FT_RENDER_MODE_NORMAL 类型,即 RGB888。

若想生成 FT_RENDER_MODE_MONO (二值化位图) 类型,操作如下:

FT_Load_Char(face, char_code, FT_LOAD_RENDER | FT_LOAD_MONOCHROME);

生成出来的二值化位图 face->glyph->bitmap->buffer[] 里的一个字节表示 8 个像素点。

*[En]*

**

#include 
#include 
#include 
#include "freetype.h"

/* 字体数据(ttf) */
typedef struct _ft_fontinfo {
    FT_Face    face;     /* FreeType库句柄对象 */
    FT_Library library;  /* 外观对象(描述了特定字样和风格,比如斜体风格等) */
    int32_t     mono;    /* 是否为二值化模式 */
} ft_fontinfo;

/* 字模格式常量定义 */
typedef enum _glyph_format_t {
    GLYPH_FMT_ALPHA, /* 每个像素占用1个字节 */
    GLYPH_FMT_MONO,  /* 每个像素占用1个比特 */
} glyph_format_t;

/* 字模(位图) */
typedef struct _glyph_t {
    int16_t  x;
    int16_t  y;
    uint16_t w;
    uint16_t h;
    uint16_t advance;  /* 占位宽度 */
    uint8_t  format;   /* 字模格式 */
    uint8_t  pitch;    /* 跨距(每行像素个数 * 单个像素所占字节数) */
    uint8_t  *data;    /* 字模数据:每个像素点占用一个字节 */
    void     *handle;  /* 保存需要释放的句柄 */
} glyph_t;

/* 获取二值化位图上像素点的值 */
uint8_t bitmap_mono_get_pixel(const uint8_t* buff, uint32_t w, uint32_t h, uint32_t x, uint32_t y) {
    /* 计算字节偏移 */
    uint32_t line_length = ((w + 15) >> 4) << 1;
    uint32_t offset = y * line_length + (x >> 3);

    /* 计算位偏移 */
    uint32_t offset_bit = 7 - (x % 8);

    const uint8_t* data = buff + offset;
    if (buff == NULL || (x > w && y > h))
        return 0;
    return (*data >> offset_bit) & 0x1;
}

/* 获取字模 */
static int font_ft_get_glyph(ft_fontinfo *font_info, wchar_t c, float font_size, glyph_t* g) {
    FT_Glyph glyph;
    FT_GlyphSlot glyf;
    FT_Int32 flags = FT_LOAD_DEFAULT | FT_LOAD_RENDER ;

    if (font_info->mono) {
        flags |= FT_LOAD_TARGET_MONO;
    }
    /* 设置字体大小 */
    FT_Set_Char_Size(font_info->face, 0, font_size * 64, 0, 96);
    //FT_Set_Pixel_Sizes(font_info->face, 0, font_size);

    /* 通过编码加载字形并将其转化为位图(保存在face->glyph->bitmap中) */
    if (!FT_Load_Char(font_info->face, c, flags)) {
        glyf = font_info->face->glyph;
        FT_Get_Glyph(glyf, &glyph);

        g->format = GLYPH_FMT_ALPHA;
        g->h = glyf->bitmap.rows;
        g->w = glyf->bitmap.width;
        g->pitch = glyf->bitmap.pitch;
        g->x = glyf->bitmap_left;
        g->y = -glyf->bitmap_top;
        g->data = glyf->bitmap.buffer;
        g->advance = glyf->metrics.horiAdvance / 64;

        if (g->data != NULL) {
            if (glyf->bitmap.pixel_mode == FT_PIXEL_MODE_MONO) {
                g->format = GLYPH_FMT_MONO;
            }
            g->handle = glyph;
        }
        else {
            FT_Done_Glyph(glyph);
        }
    }
    return g->data != NULL || c == ' ' ? 1 : 0;
}

int main(int argc, const char *argv[])
{
    ft_fontinfo   font_info;         /* 字库信息 */
    long int      size = 0;          /* 字库文件大小 */
    unsigned char *font_buf = NULL;  /* 字库文件数据 */

    /* 加载字库文件存入font_buf */
    FILE *font_file = fopen("../font/default.ttf", "rb");
    if (font_file == NULL) {
        printf("Can not open font file!n");
        getchar();
        return 0;
    }
    fseek(font_file, 0, SEEK_END); /* 设置文件指针到文件尾,基于文件尾偏移0字节 */
    size = ftell(font_file);       /* 获取文件大小(文件尾 - 文件头  单位:字节) */
    fseek(font_file, 0, SEEK_SET); /* 重新设置文件指针到文件头 */

    font_buf = (unsigned char*)calloc(size, sizeof(unsigned char));
    fread(font_buf, size, 1, font_file);
    fclose(font_file);

    font_info.mono = 1;  /* 设置为二值化模式 */

    /* 初始化FreeType */
    FT_Init_FreeType(&(font_info.library));

    /* 从font_buf中提取外观 */
    FT_New_Memory_Face(font_info.library, font_buf, size, 0, &(font_info.face));

    /* 设置字体编码方式为UNICODE */
    FT_Select_Charmap(font_info.face, FT_ENCODING_UNICODE);

    glyph_t g;
    wchar_t c = L'a';
    float   font_size = 36;  /* 设置字体大小 */
    font_ft_get_glyph(&font_info, c, font_size, &g);  /* 获取字模 */

    /* 打印字模信息 */
    int i = 0, j = 0;
    if (g.format == GLYPH_FMT_MONO) {
        for (j = 0; j < g.h; ++j) {
            for (i = 0; i < g.w; ++i) {
                uint8_t pixel = bitmap_mono_get_pixel(g.data, g.w, g.h, i, j);
                if (pixel) {
                    printf("0");
                }
                else {
                    printf(" ");
                }
            }
            printf("n");
        }
    }
    else if (g.format == GLYPH_FMT_ALPHA) {
        for (j = 0; j < g.h; ++j) {
            for (i = 0; i < g.w; ++i)
                putchar(" .:ioVM@"[g.data[j*g.w + i] >> 5]);
            putchar('n');
        }
    }

    /* 释放资源 */
    FT_Done_Glyph(g.handle);
    FT_Done_FreeType(font_info.library);
    free(font_buf);
    getchar();

    return 0;
}

Original: https://www.cnblogs.com/zhoug2020/p/16484414.html
Author: 莫水千流
Title: FreeTpye库学习笔记:将矢量字体解析为位图



相关阅读

Title: 更换 Kubernetes Storage Class 所使用的 NAS

最近我们在 kubernetes 集群上部署 rabbitmq 时遇到一个场景,需要将 rabbitmq 使用的 persistent volume 从阿里云性能型 nas 切换为极速型 nas,因为只有极速型 nas 才支持快照与备份功能。

需要修改的参数是 server,但 kubernetes 不允许直接通过 kubectl edit 命令修改,会报错"Forbidden: updates to parameters are forbidden",只能通过资源清单文件强制替换。

资源清单:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: sc-nas-production-pvs
  annotations:
    storageclass.kubernetes.io/is-default-class: "true"
mountOptions:
  - nolock,tcp,noresvport
  - vers=3
parameters:
  volumeAs: subpath
  server: xxxxxx.cn-hangzhou.extreme.nas.aliyuncs.com:/production/k8s-pvs/
provisioner: nasplugin.csi.alibabacloud.com
reclaimPolicy: Retain

强制替换 StorageClass 的命令:

 kubectl replace -f nas-production-pvs.yaml --force

PersistentVolumeClaim 也不让直接修改,会报错"Forbidden: spec.persistentvolumesource is immutable after creation",也需要采用资源清单文件强制替换的方式。

导出已有 pvc 的资源清单

kubectl get pvc data-rabbitmq-0 -o yaml > data-rabbitmq-0.yaml

删除 data-rabbitmq-0.yaml 中的 status、volumeMode、volumeName 部分,得到下面的资源清单文件

piVersion: v1
kind: PersistentVolumeClaim
metadata:
  annotations:
    pv.kubernetes.io/bind-completed: "yes"
    pv.kubernetes.io/bound-by-controller: "yes"
    volume.beta.kubernetes.io/storage-provisioner: nasplugin.csi.alibabacloud.com
  labels:
    app.kubernetes.io/instance: rabbitmq
    app.kubernetes.io/name: rabbitmq
  name: data-rabbitmq-0
  namespace: production
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 8Gi
  storageClassName: sc-nas-production-pvs

然后用这个 yaml 文件强制替换已有的 pvc

kubectl replace -f data-rabbitmq-0.yaml --force

Dynamic volume provisioning 会自动基于新的 nas 创建 PersistentVolume

NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS     CLAIM                                    STORAGECLASS            REASON   AGE
nas-d0c28e12-39b5-4b4f-b893-03dd28d1cef8   8Gi        RWO            Retain           Bound      production/redis-data-redis-master-0     sc-nas-production-pvs            174d

注:在此期间需要重启 rabbitmq 的 pods

kubectl rollout restart statefulset/rabbitmq
  • 将原 nas 中的 rabbitmq 数据文件复制到新 nas 中
cp -r /nas/k8s-pvs/nas-f17c6bb8-5848-46a7-97bb-80c1fa52a619/* /nas-k8s/production/k8s-pvs/nas-e4324c56-af56-440b-a530-6671b3bfe879

重启 rabbitmq 的 pods 以使用新的数据文件

kubectl rollout restart statefulset/rabbitmq

注:迁移数据文件这一步由于操作时 rabbitmq 中没有实际数据,所以不能确定这样迁移是否切实可行。

Original: https://www.cnblogs.com/dudu/p/15637344.html
Author: dudu
Title: 更换 Kubernetes Storage Class 所使用的 NAS