最近有国内新客户抱怨我们产品显示的原理图太不专业了,在原理图上使用宋体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测试代码如下
proc zoominit { c { zfact { 1.1}}} {
# save zoom state in a global variable with the same name as the canvas handle
upvar #0 $c data
set data(zdepth) 1.0
}
proc zoom { c fact} {
upvar #0 $c data
# zoom at the current mouse position
set x [$c canvasx [expr {[winfo pointerx $c] - [winfo rootx $c]}]]
set y [$c canvasy [expr {[winfo pointery $c] - [winfo rooty $c]}]]
$c scale all $x $y $fact $fact
# save new zoom depth