QFont-使用外部字体文件的问题

news2025/1/21 9:37:20

        我们程序里面定义了某个结构体(这里简单描述为AStruct),AStruct包含了一个QFont 类型的成员变量:

struct AStruct {
    QFont ft;
};

        在具体业务上,AStruct中的QFont会被传递给QPainter去绘制文本。           

        保存工程/加载工程时时,会对AStruct对象进行序列化/反序列化操作:

        
struct AStruct {
    QFont ft;

    QString serialize() {
        QByteArray buf;
		QDataStream in(&buf, QIODevice::WriteOnly);
		in << font;
        return buf.toBase64();
    }

    void deserialize(const QString& d) {
        QByteArray buf = QByteArray::fromBase64(d.toLatin1());
        QDataStream out(&buf, QIODevice::ReadOnly);
		out >> font;
    }
};

        正常情况下,这套序列化/反序列化以及QPainter绘制文本都没有什么问题。但是客户在自己机器上安装了一个新的字体文件后(字体X),问题来了:

        在AStruct的编辑界面,用户在QFontComboBox里面选择了X字体,QPainter绘制正常。然后保存工程(序列化AStruct),再重新打开工程(反序列化AStruct),QPainter绘制异常,没有使用X字体来绘制文本。

        当时的第一反应就是可能序列化或者反序列出问题了,到底哪里出问题了呢?

       先排查序列化吧!  由于在第一次在编辑界面对AStruct设置为X字体后,QPainter绘制是正确的,说明那一次QPainter使用的字体是正确的,所以我把QPainter中的字体获取出来后,使用和AStruct中同样的方法对QFont进行序列化,得到一个序列化后的字符串,再和我工程里面存储的字符串进行比较,发现发现二者在后面一段有差异(标红的部分):

  工程文件里面的:      AAAAEnm5U2tOZmzViExOZnuAT1MACv9ARAAAAAAAAP8FAAEAMhAAAAEAAAAAAAAAAAAAAAAAA=

 从QPainter的QFont序列化出来的:

AAAAEnm5U2tOZmzViExOZnuAT1MACv9ARAAAAAAAAP8FAAEAMhAAAAEAAAAAAAAAAAAAAAAAAQAAABJ5uVNrTmZs1YhMTmZ7gE9TAAo=

想要知道这部分数据存的到底是什么,只能看QFont的的源码了:

QDataStream &operator<<(QDataStream &s, const QFont &font)
{
    if (s.version() == 1) {
        s << font.d->request.family.toLatin1();
    } else {
        s << font.d->request.family;
        if (s.version() >= QDataStream::Qt_5_4)
            s << font.d->request.styleName;
    }

    if (s.version() >= QDataStream::Qt_4_0) {
        // 4.0
        double pointSize = font.d->request.pointSize;
        qint32 pixelSize = font.d->request.pixelSize;
        s << pointSize;
        s << pixelSize;
    } else if (s.version() <= 3) {
        qint16 pointSize = (qint16) (font.d->request.pointSize * 10);
        if (pointSize < 0) {
            pointSize = (qint16)QFontInfo(font).pointSize() * 10;
        }
        s << pointSize;
    } else {
        s << (qint16) (font.d->request.pointSize * 10);
        s << (qint16) font.d->request.pixelSize;
    }

    s << (quint8) font.d->request.styleHint;
    if (s.version() >= QDataStream::Qt_3_1) {
        // Continue writing 8 bits for versions < 5.4 so that we don't write too much,
        // even though we need 16 to store styleStrategy, so there is some data loss.
        if (s.version() >= QDataStream::Qt_5_4)
            s << (quint16) font.d->request.styleStrategy;
        else
            s << (quint8) font.d->request.styleStrategy;
    }
    s << (quint8) 0
      << (quint8) font.d->request.weight
      << get_font_bits(s.version(), font.d.data());
    if (s.version() >= QDataStream::Qt_4_3)
        s << (quint16)font.d->request.stretch;
    if (s.version() >= QDataStream::Qt_4_4)
        s << get_extended_font_bits(font.d.data());
    if (s.version() >= QDataStream::Qt_4_5) {
        s << font.d->letterSpacing.value();
        s << font.d->wordSpacing.value();
    }
    if (s.version() >= QDataStream::Qt_5_4)
        s << (quint8)font.d->request.hintingPreference;
    if (s.version() >= QDataStream::Qt_5_6)
        s << (quint8)font.d->capital;
    if (s.version() >= QDataStream::Qt_5_13)
        s << font.d->request.families;
    return s;
}

通过调试发现,序列化AStruct中的QFont和序列化QPainter中的QFont,差异就在上面这个函数的最后一个if:

 if (s.version() >= QDataStream::Qt_5_13)
        s << font.d->request.families;

AStruct序列化时,request.families为空,QPainter的QFont序列化时request.families不为空。

但是QPainter的字体明明是通过setFont()方法把AStruct的QFont设置进去的,怎么序列化就不一样了呢? 莫非是QPainter里面有偷偷摸摸干了啥? 一查代码,还真是:

/** QPainter::setFont() ***************************************/
void QPainter::setFont(const QFont &font)
{
    Q_D(QPainter);

#ifdef QT_DEBUG_DRAW
    if (qt_show_painter_debug_output)
        printf("QPainter::setFont(), family=%s, pointSize=%d\n", font.family().toLatin1().constData(), font.pointSize());
#endif

    if (!d->engine) {
        qWarning("QPainter::setFont: Painter not active");
        return;
    }

    d->state->font = QFont(font.resolve(d->state->deviceFont), device());
    if (!d->extended)
        d->state->dirtyFlags |= QPaintEngine::DirtyFont;
}

/** QFont::resolve() *****************************************/
QFont QFont::resolve(const QFont &other) const
{
    if (resolve_mask == 0 || (resolve_mask == other.resolve_mask && *this == other)) {
        QFont o(other);
        o.resolve_mask = resolve_mask;
        return o;
    }

    QFont font(*this);
    font.detach();
    font.d->resolve(resolve_mask, other.d.data());

    return font;
}

/** QFontPrivate::resolve() *************************/
void QFontPrivate::resolve(uint mask, const QFontPrivate *other)
{
    Q_ASSERT(other != nullptr);

    dpi = other->dpi;

    if ((mask & QFont::AllPropertiesResolved) == QFont::AllPropertiesResolved) return;

    // assign the unset-bits with the set-bits of the other font def
    if (! (mask & QFont::FamilyResolved))
        request.family = other->request.family;

    if (!(mask & QFont::FamiliesResolved)) {
        request.families = other->request.families;
        // Prepend the family explicitly set so it will be given
        // preference in this case
        if (mask & QFont::FamilyResolved)
            request.families.prepend(request.family);
    }

    if (! (mask & QFont::StyleNameResolved))
        request.styleName = other->request.styleName;

    if (! (mask & QFont::SizeResolved)) {
        request.pointSize = other->request.pointSize;
        request.pixelSize = other->request.pixelSize;
    }
    ..........................................
}

上面贴出了调用QPainter::setFont()时和字体相关的几个关键函数,调用时序为:

QPainter::setFont() -> QFont::resovle() ->QFontPrivate::resolve()

看QFontPrivate::resolve(),里面有这么一段:

if ((mask & QFont::AllPropertiesResolved) == QFont::AllPropertiesResolved) return;

    // assign the unset-bits with the set-bits of the other font def
    if (! (mask & QFont::FamilyResolved))
        request.family = other->request.family;

    if (!(mask & QFont::FamiliesResolved)) {
        request.families = other->request.families;
        // Prepend the family explicitly set so it will be given
        // preference in this case
        if (mask & QFont::FamilyResolved)
            request.families.prepend(request.family);
    }

说人话就是:

如果字体的属性掩码不是QFont::AllPropertiesResolved,那么就需要根据属性掩码对没有复制的属性进行复制。于是马上有了对FamiliesResolved属性的处理: 如果FamiliesResolved没有赋值,那么就用Family属性填充families。   

既然QPainter每次设置字体都会调用一遍QFont::resolve()来填充families字段,按理说重新打开工程之后,使用反序列化得到的QFont设置给QPainter时,也会自动填充families才对啊,为什么绘制就不对了呢?

注意上面对QFont的属性填充有个判断条件:

 也就是说当mask没有覆盖了所有字体属性时,才会进入到下面的逻辑。那么这个mask是如何赋值的呢?

mask的赋值有几种方式,一种是调用QFont的resolve()函数,一种是在QFont的几个构造函数中自动赋值,另一种则是QDataStream反序列化中赋值。我们要关心的正是QDataStream的反序列化:

QDataStream &operator>>(QDataStream &s, QFont &font)
{
    font.d = new QFontPrivate;
    font.resolve_mask = QFont::AllPropertiesResolved;

    quint8 styleHint, charSet, weight, bits;
    quint16 styleStrategy = QFont::PreferDefault;

    if (s.version() == 1) {
        QByteArray fam;
        s >> fam;
        font.d->request.family = QString::fromLatin1(fam);
    } else {
        s >> font.d->request.family;
        if (s.version() >= QDataStream::Qt_5_4)
            s >> font.d->request.styleName;
    }

    if (s.version() >= QDataStream::Qt_4_0) {
        // 4.0
        double pointSize;
        qint32 pixelSize;
        s >> pointSize;
        s >> pixelSize;
        font.d->request.pointSize = qreal(pointSize);
        font.d->request.pixelSize = pixelSize;
    } else {
        qint16 pointSize, pixelSize = -1;
        s >> pointSize;
        if (s.version() >= 4)
            s >> pixelSize;
        font.d->request.pointSize = qreal(pointSize / 10.);
        font.d->request.pixelSize = pixelSize;
    }
    s >> styleHint;
    if (s.version() >= QDataStream::Qt_3_1) {
        if (s.version() >= QDataStream::Qt_5_4) {
            s >> styleStrategy;
        } else {
            quint8 tempStyleStrategy;
            s >> tempStyleStrategy;
            styleStrategy = tempStyleStrategy;
        }
    }

    s >> charSet;
    s >> weight;
    s >> bits;

    font.d->request.styleHint = styleHint;
    font.d->request.styleStrategy = styleStrategy;
    font.d->request.weight = weight;

    set_font_bits(s.version(), bits, font.d.data());

    if (s.version() >= QDataStream::Qt_4_3) {
        quint16 stretch;
        s >> stretch;
        font.d->request.stretch = stretch;
    }

    if (s.version() >= QDataStream::Qt_4_4) {
        quint8 extendedBits;
        s >> extendedBits;
        set_extended_font_bits(extendedBits, font.d.data());
    }
    if (s.version() >= QDataStream::Qt_4_5) {
        int value;
        s >> value;
        font.d->letterSpacing.setValue(value);
        s >> value;
        font.d->wordSpacing.setValue(value);
    }
    if (s.version() >= QDataStream::Qt_5_4) {
        quint8 value;
        s >> value;
        font.d->request.hintingPreference = QFont::HintingPreference(value);
    }
    if (s.version() >= QDataStream::Qt_5_6) {
        quint8 value;
        s >> value;
        font.d->capital = QFont::Capitalization(value);
    }
    if (s.version() >= QDataStream::Qt_5_13) {
        QStringList value;
        s >> value;
        font.d->request.families = value;
    }
    return s;
}

看,这个函数第二行就把mask赋值成了font.resolve_mask = QFont::AllPropertiesResolved,但是又因为AStruct的QFont序列化时,request.families为空,所以在反序列化时没有任何数据解析出来。

至此,问题就比较明确了:

AStruct的QFont序列化时,families属性为空;反序列化时,families属性也为空,同时,QFont的mask被设置成了font.resolve_mask = QFont::AllPropertiesResolved;   于是当我将反序列化得到的AStruct的QFont设置给QPainter后,QPainter调用QFont::resolve()方法无法填充families。最后QPainter使用字体失败。

如何解决这个问题?

这个问题的根本原因在于AStruct中的QFont属性不完整,我们得想办法让他完整。其实QPainter已经给出了解决方案:调用QFont::resolve()来填充属性。 当然,调用resolve还有一些坑,这里我直接给出我的代码。很简单:

AStruct astru;
connect(ui.fontComboBox, &QFontComboBox::currentFontChanged, this, [this, astru](const QFont& ft) {

        /*有问题的写法: astru.font中的families属性为空 
        astru.font = ft;
        */

        /*修正后的写法: 通过调用resolve给families属性赋值*/
		astru.font = QFont(ft.family()).resolve(ft);
});

        

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/43773.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

用Python写个爬虫,赚了!

前言 编程语言排行榜(Tiobe)发布了11月新榜&#xff0c;Python仍领先Java和C&#xff0c;在全球众多的老牌编程语言中&#xff0c;以其他对手无法企及的极高份额稳居榜单第一位。 榜单数据是客观真实的&#xff0c;当下Python的使用者越来越多&#xff0c;已经远超使用其他十…

【Linux初阶】认识冯诺依曼结构

&#x1f31f;hello&#xff0c;各位读者大大们你们好呀&#x1f31f; &#x1f36d;&#x1f36d;系列专栏&#xff1a;【Linux初阶】 ✒️✒️本篇内容&#xff1a;计算机的冯诺依曼组成结构、冯诺依曼结构的数据交换特性&#xff0c;以宏观视角理解冯诺依曼下的网络数据传输…

Spring Cloud Alibaba 容器化部署最佳实践 | 本地部署版本详解

环境声明 在运行本地示例之前&#xff0c;需要保证本机具备以下的基础环境&#xff0c;如果您的本地没有当前的环境&#xff0c;下面会一步步进行搭建&#xff0c;演示搭建过程。 Nacos 服务端Seata 服务端RocketMQ 服务端MySQL 服务端 可在国内https://gitee.com/mirrors/S…

低代码在离散制造业的实践

大家上午好&#xff0c;非常感谢明道云给我这个机会&#xff0c;来展示一下这个上海电气在明道云上面的应用。我的题目是《低代码在离散制造业的实践》。 引入明道云的背景 首先介绍一下我们电气数字科技有限公司。它是在2018年的时候&#xff0c;上海电气集团内部成立的数科…

捷码全新快速表单功能解读

在最近的更新中&#xff0c;捷码迎来了比较多的功能上新&#xff0c;其中最受大家关注的是全新的表单功能。无论是从功能丰富度还是操作便利性上都做了大量设计。本文将从重点功能设置操作流程上进行演示说明。 表单控件和关联 ——1对1、1对n、n对n 支持多场景 首先是表单控…

航空货运数据挖掘那些事|航班换季

目录前言航班换季航班换季对航空货运业务有哪些影响参考文献前言 就在上个月月底&#xff0c;即2022年10月29日到2022年10月30日&#xff0c;中国民航完成了2022年的夏秋季航季到2022/23年冬春航季切换工作&#xff0c;俗称航班换季&#xff0c;那么何为航班换季呢&#xff0c…

流媒体传输 - RTMP 协议

RTMP 是 Real Time Messaging Protocol&#xff08;实时消息传输协议&#xff09;的首字母缩写。它是由 Adobe 公司提出的一种应用层的协议&#xff0c;用来解决多媒体数据传输流的多路复用&#xff08;Multiplexing&#xff09;和分包&#xff08;packetizing&#xff09;的问…

氨基苯酚/多巴胺仿生修饰碳纳米管/α-氧化铝/ CNTs-Ag纳米复合材料

小编下面和大家来看间氨基苯酚/多巴胺仿生修饰碳纳米管/α-氧化铝/ CNTs-Ag纳米复合材料的制备&#xff01; 多巴胺仿生修饰材料的研究&#xff1a; 通过表面聚多巴胺层与银离子的螯合作用,通过表面聚多巴胺与银离子的螯合作用,利用葡萄糖作还原剂,在聚多巴胺修饰的聚酯纤维表…

HarmonyOS应用API手势方法-PanGesture

描述&#xff1a;用于触发拖动手势事件&#xff0c;滑动的最小距离为5vp时拖动手势识别成功。 Api&#xff1a;从API Version 7开始支持 接口&#xff1a; PanGesture(value?: { fingers?: number; direction?: PanDirection; distance?: number } | PanGestureOptions)…

IT6225/IT6225B Type-C转HDMI 4K60设计方案

ITE&#xff08;新联阳&#xff09;的IT6225/IT6225B&#xff0c;是一款Type-C转HDMI 460K的视频转换芯片 通过USB Type-C连接器将DP RX视频信号转换为HDMI/DVI TX视频信号。DP信号转接只用2lane&#xff0c;另外2lane可以输出USB 3.0/3.1信号&#xff0c;同时兼容PD 3.0&…

Zabbix自动监控windows端口(主动监控方式)

第一部分&#xff1a;服务器上的操作&#xff08;脚本&配置&#xff09; 创建脚本文件 在客户端新建discovertcpport.bat文件&#xff0c;放在C:\Program Files\Zabbix Agent目录中&#xff0c;内容如下&#xff1a; echo off echo { echo "data":[ f…

[附源码]计算机毕业设计springboot创意摄影交流平台

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

python面向对象进阶-继承、多态

继承 python面向对象的继承指的是多个类之间的所属关系&#xff0c;及子类默认继承父类的所有属性和方法#父类A class A(object):def __init__(self):self.num 1#定义成员函数&#xff1a;方法def info_print(self):print(self.num)#子类&#xff0c;继承父类 class B(A):pass…

PaddlePaddle自然语言处理总结

11月28日 week14 周一 910节 物联201 2008070101 本文总结&#xff1a; 1.介绍词向量引入对自然语言处理有着至关重要的作用&#xff1b; 2.介绍了word2vec算法&#xff1b; 3.介绍CBOW算法的实现&#xff1b; 4.介绍Skip-Gram算法的实现。 基本流程&#xff1a; 1.数据预处理&…

​企业该如何做好源代码防泄密工作

​企业该如何做好源代码防泄密工作 随着企业信息化发展的日益增长&#xff0c;软件行业厂商之间的竞争也愈加白热化&#xff0c;加上国内对知识产权的不够重视、山寨模仿产品的横行。保护源代码、保证企业的核心竞争力&#xff0c;成为众多软件研发企业的第一要务。那么企业应…

智能制造云办公 v3.8.10 发布,团队管理更新

智能制造云办公系统 [SpringBoot2 - 快速开发平台]&#xff0c;适用于制造业、建筑业、汽车行业、互联网、教育、政府机关等机构的管理。包含文件在线操作、工作日志、多班次考勤、CRM、ERP 进销存、项目管理、EHR、拖拽式生成问卷、日程、笔记、工作计划、行政办公、薪资模块、…

FusionCompute五个网络平面

文章目录前言一、五个网络平面二、管理网络平面三、存储网络平面四、业务网络平面五、VIMS心跳网络平面前言 FusionCompute 五个网络平面&#xff0c;最少6网口&#xff08;6电或4电2光&#xff09;&#xff0c;最佳8块网卡&#xff08;8个电口或6电口2光口&#xff09; 一套存…

【Mybatis编程:根据id查询相册数据详情】

目录 1.书写SQL语句 2. 在AlbumMapper.java中添加抽象方法 3. 在AlbumMapper.xml中配置SQL语句 4. 在AlbumMapperTests.java中编写并执行测试 1.书写SQL语句 需要执行的SQL语句大致是&#xff1a; SELECT id, name, description, sort FROM pms_album WHERE id? 由于不需要…

Maven下载及环境搭建

目录 一、maven简介 二、maven的下载 1、找到maven官网 2、点击下载 &#xff0c;下载后解压到此电脑 三、配置环境环境变量 1、找到mybatis解压到的目录 2、打开电脑&#xff0c;找到高级系统设置&#xff0c;点击环境变量 3、点击新建&#xff0c;命名为MAVEN_HOME&…

[附源码]计算机毕业设计javaee家教管理系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…