Tcl/tk缩放Truetype字体时的精度问题

博客讲述了在Tcl/tk中使用Windows GDI渲染Truetype字体时,由于精度问题导致缩放后文字位置不准确。在缩放比例达到一定值时,Truetype字体大小出现偏差,而使用Freetype的Outline字体则无此问题。作者建议通过替换GDI APIs为DirectWrite的浮点型APIs来解决这一问题。

最近有国内新客户抱怨我们产品显示的原理图太不专业了,在原理图上使用宋体GB2312设计好中文图表,经过几次缩放时,表格内的文字居然会跑到表格外边,更要命的是打印出来的文档也存在同样的问题。 我研究了一下,原来又是Windows GDI Text APIs的一个大坑!

问题详细描述

用Tcl/tk script可以很容易地重现这个问题。如下图,第一行文字使用的Arial字体rendering by GDI APIs, 第二行文字使用的是OF Helvetica字体rendering by Freetype APIs,两行文字都用和字体无关的方式加了边框。

  • 刚运行脚本时,两行文字都是8pt,虽然显示效果不一样,但看着是一样大。 

  • 放大几次后,第一行的GDI文字明显跑到框外了。 

再看一下这时的log: 
font:-family Arial -size 20.118148965022 -weight normal -slant roman -underline 0 -overstrike 0 -type truetype 
font:-family {OF Helvetica}
 -size 19.53125 -weight normal -slant roman -underline 0 -overstrike 0 -type outline 
zoom fact:2.44140625

缩放比例2.44140625,outline字体的大小是19.53125ot,正好等于 8 * 2.44140625,没有一点精度损失;而truetype字体的大小是20.118148965022pt,明显偏大了。

原因分析

Tcl tk在Windows上使用GDI font engine来渲染显示Truetype字体。GDI Text APIs 的font size必须是int类型,在代码调用过程中,需要把界面指定的phsicial font size(pt) 转换为 logical font size,从而导致了精度损失;作为对比,如果使用我们产品内置的Outline字体就没有类似的问题,因为Outline字体是我们过去为了支持跨平台,自己移植的Freetype字体,对应使用的是Freetype font engine, Freetype APIs的font size都是float类型的,从phsicial font size 转换为 logical font size时不会产生精度损失。为了支持中文,用户只能使用基于GDI render 的truetype字体,因为Freetype字体库中还没有支持中文的字体,这次看来必须要对Tcl/tk的font engine做个大手术,用DirectWrite float-based APIs 更换老旧的GDI APIs了。

tcl/tk测试代码如下
 
  
  
  
  1. proc zoominit { c { zfact { 1.1}}} {
  2. # save zoom state in a global variable with the same name as the canvas handle
  3. upvar #0 $c data
  4. set data(zdepth) 1.0
  5. }
  6. proc zoom { c fact} {
  7. upvar #0 $c data
  8. # zoom at the current mouse position
  9. set x [$c canvasx [expr {[winfo pointerx $c] - [winfo rootx $c]}]]
  10. set y [$c canvasy [expr {[winfo pointery $c] - [winfo rooty $c]}]]
  11. $c scale all $x $y $fact $fact
  12. # save new zoom depth
  13. set data(
<think> 我们正在分析用户的问题,用户提到在执行kysec_get命令后输出"none:none:original"这样的信息,并且特别关注字体文件的相关信息。用户希望理解这三个字段的含义。 根据银河麒麟安全机制文档[^1]: 1. kysec_get命令输出的格式通常为:`[标签1]:[标签2]:[标签3]` 2. 这三个字段分别对应三种安全标签: - 第一个字段:执行控制标签(exectl) - 第二个字段:保护标签(protect) - 第三个字段:用户身份标签(userid) 因此,输出"none:none:original"的含义是: - 第一个"none":表示该文件没有设置执行控制标签(即非可执行文件,或者未标记为特殊可执行文件) - 第二个"none":表示该文件没有设置保护标签(即该文件受写保护,可以被修改或删除) - "original":表示该文件被标记为原始文件(即来自系统原始安装,未被篡改) 具体来说,对于字体文件(/usr/share/fonts下的文件): 1. 由于字体文件通常是数据文件,是可执行程序,所以执行控制标签为none。 2. 字体文件在默认安装通常受写保护(除非特别设置),所以保护标签为none。 3. 由于字体文件是系统安装的原始文件,所以用户身份标签为original,表明该文件是系统原始文件,未被用户修改。 引用说明: [^1]: 银河麒麟安全标签管理手册 v4.5 </think> ### 🔍 理解 `kysec_get` 输出的字体文件信息 `none:none:original` 在银河麒麟系统的安全机制中,`kysec_get` 命令输出的三个字段对应文件的三种安全属性: **`执行控制标签 : 保护标签 : 用户身份标签`** #### 📊 字体文件 `/usr/share/fonts` 的输出解析 | 字段位置 | 标签类型 | 值 `none` 含义 | 值 `original` 含义 | |----------|----------------|----------------------------------------|-----------------------------------------| | **第一字段** | 执行控制标签 | 文件**允许**作为可执行文件运行(默认)[^1] | - | | **第二字段** | 保护标签 | 文件**未受写保护**(可被修改/删除)[^2] | - | | **第三字段** | 用户身份标签 | - | 文件为**系统原始安装文件**(未被篡改)[^1] | #### 💡 具体含义 1. **`none:none`** - 字体文件是**普通数据文件**(非可执行程序) - 字体文件**未被写保护**(可通过 `kysec_set` 添加保护) 2. **`original`** - 文件来源可信(来自系统官方安装包) - 文件内容未被篡改(与初始安装一致) - 此标签由系统自动标记 #### ️ 安全建议 若要保护字体文件被修改: ```bash # 添加写保护标签(需root权限) sudo kysec_set -p +protect /usr/share/fonts/* # 验证保护状态 kysec_get /usr/share/fonts/arial.ttf # 输出变为:none:protect:original ``` > 📌 此文件将无法被修改/删除(类似 `chattr +i` 效果)[^2] --- ### 🌐 技术原理图解 ``` 系统安全框架 ├─ 执行控制层 (exectl) → none: 禁止作为二进制执行 ├─ 数据保护层 (protect) → none: 文件可读写 └─ 来源验证层 (userid) → original: 官方安装文件 ``` --- ### 📝 相关操作示例 | 场景 | 命令 | 输出示例 | |--------------------------|-------------------------------|-----------------------| | 查看单个字体文件 | `kysec_get /usr/share/fonts/arial.ttf` | `none:none:original` | | 添加写保护 | `sudo kysec_set -p +protect /usr/share/fonts/*` | - | | 移除原始标记(推荐) | `sudo kysec_set -u - /usr/share/fonts/*` | `none:none:none` | ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值