Archive for the ‘Programming’ Category.

TextEdit/UCD R5 与 Cocoa Text System

更新: TextEdit/UCD 的代码现在可以在 http://gitorious.org/projects/textedit-ucd/ 找到。

TextEdit/UCD 的第 5 个版本发布了,TextEdit/UCD 开发的目标是尽可能解决所有 TextEdit 固有的中文处理问题,但并不改变 TextEdit 原有的轻量小巧。

在开发这个版本中,我发现了一个 Cocoa Text System 的固有问题:还没有下载的朋友可以先打开自己机器上的 TextEdit,输入一个汉字,一个英文字母,如“中a”,这时汉字会以默认的中文字体显示,英文字母会以 Preferences 中设定的 Plain Text 字体 (如 Monaco) 显示,此时按下 cmd-’+’ 增大一号字体,然后再输入一个英文字母,就会发现这个新的英文字母居然改用中文字体显示了?

为什么呢?在 TextEdit 中花了一段时间重载各个类,加上 gdb 分析,结果发现,其实在 Make Text Bigger/Make Text Smaller 时,调用 [NSFontManager modifyFont:],这个 modifyFont: 方法会将 -changeFont: 这个 action 发送到 responder chain 中,而此时的 NSTextView 收到这个 action 以后,居然会将 [NSFontManager sharedInstance]selectedFont 修改为这个 TextView 的 TextStorage 中第一个字符使用的字体。于是就导致了已选字体的变化。这其实不是我们期望的结果。

然而,由于事实上 NSTextView 的 changeFont 实现可能很复杂,它要逐个分析所有的 run,逐个将这个 run 的字体送到 NSFontManager 中 convertFont 获得新的字体。既然我们无法直接修改这个 changeFont 的代码,就只能用重载的方法,写一个新的 NSTextView 的子类,重载这个函数,在调用基类的 changeFont 之前,先给整个文档的开头加上一个用纯文本字体的新字符,然后在调用完基类 changeFont 之后再把它删除。

这是一个看起来有些 dirty 的方法,不过很有用,也算是“曲线救国”,用欺骗的方式使之有正确的效果。

Chmox 的一些修正

Chmox 是 Mac OS X 上常用的 chm 阅读软件,它最后一个版本是 2006-10-28 发布的 0.4β,这也是它唯一的一个 Universal Binary 的版本,此后作者就再也没有过更新,因为我和许多 Mac 用户一样,为缺少好用的 chm 所困扰,所以便尝试来对此做一些修正。

主要的更新是参照 ChmSee 的做法修正了一些编码判断的问题,包括目录的编码和显示内容的编码。欢迎对此有兴趣的朋友将 Chmox 无法打开或者打开错误的 chm 发给我 (当然,文件大小尽可能小),我会尽力修正的。

下面是最近的更新记录 (版本号延续作者原来的定义进行递增):

Chmox 0.4.2

更新:

- 修正某些 chm 文件无法正确载入页面的问题 (faithprice 报告)
- 没有目录时不打开 Drawer
- 提供简单的页内搜索功能

Chmox 0.4.1*

注: 版本号为我按照 http://chmox.sourceforge.net/ 原有版本更新
的,所以这不是官方版本号 (似乎作者本人已经停止维护了)。

更新:

- 更新到 chmlib 0.39
- 修正一些编译警告
- 使用 chm 文件自带的 LCID 信息来判断 CHM 目录的编码,修正在
  解析目录时的一些常见的编码判断错误

下载链接为: Chmox-0.4.2.dmg

更新: 刚刚设立了一个 git 代码仓库,请用 git clone git://gitorious.org/chmox/mainline.git 获取最新代码,访问这里了解项目最新开发进度。

Cocoa 的 NSString 解码错误处理

在使用 Safari 的时候,我们会注意到一个很常见的乱码问题,如下图:

Safari Decode Error

这是在打开 http://att.newsmth.net/att.php?p.719.214628.536.png 这样的图片链接时,Safari 错误的判断了这个图片文件的文件名造成的。而为什么会有这样的错误判断呢?

其实 Safari 使用的是 Cocoa 框架 URL Loading 架构中的 NSURLResponse 类的 suggestedFilename 方法实现的。

而这个方法,其实就是解析 HTTP 首部中的 Content-Disposition 域里的 filename 部分完成的,比如下面这个首部:

$ curl -I http://att.newsmth.net/att.php?p.719.214628.536.png
HTTP/1.1 200 OK
....
Content-Disposition: inline;filename=ͼƬ_6.png
....

显然这是乱码,可奇怪的是,这和我们在上面的图中看到的乱码又不一样,这是为什么呢?

假如将它作为 GBK 来解码就清楚了:

$ curl -I http://att.newsmth.net/att.php?p.719.214628.536.png | iconv -f gbk -t utf-8
HTTP/1.1 200 OK
....
Content-Disposition: inline;filename=图片_6.png
....

哦,原来是 GBK 编码的“图片_6.png”,可是这个文件名怎么会变成开头图片中那种形式的乱码呢?其实写一段 Cocoa 程序就可以发现:

#import <Foundation/Foundation.h>

int main()
{
    const char *bytes = "图片_6.png";
    NSString *str = [[NSString alloc] initWithCString: bytes
                                             encoding: NSASCIIStringEncoding];
    NSLog(@”str: %@”, str);
    [str release];

    return 0;
}

(用 GBK 编码保存) 这个程序的执行结果就是输出开头那段乱码,原来 NSURLResponse 把 Content-Disposition 中的 filename 当成 ASCII 处理了,怪不得会乱码。

可是也不能怪 NSURLResponse,毕竟服务器没有提供任何编码的信息,而 RFC 2183 中也明确说明,不应该在 filename 中使用任何 ASCII 以外的字符,用了就是后果自负了。

那假如我们要写一个自己的客户端 (或者尝试修正 Safari 的错误行为),该怎么修正已经被按照 ASCII 错误解码的 NSString 呢?

因为 NSString 本身是按照 UTF-16 编码的,所以如果逐个字符地观察这个错误解码后的 NSString:

int max = [str length];

int i;
for (i = 0; i < max; i++)
{
    unichar ch = [str characterAtIndex: i];
    printf(”%x “, ch);
}

我们可以得到:

cd bc c6 ac 5f 36 2e 70 6e 67

这样一串输出,5f 36 2e 70 6e 67 就是 _6.png,比较好认,前面的 cd bc c6 ac 是什么呢?一查,原来是“图”和“片”这两个字的 GBK 编码。

这就好理解了:NSString 一开始把一段 GBK 编码的字节流逐个字节地按照 8bit-ASCII 处理了,原本 10 个字节对应的是 7 个字符,结果被错误地解码为了 10 个字符,所以我们要把它转换回去,首先是要还原回原来的那段字节流:

int max = [str length];
char *nbytes = malloc(max + 1);

int i;
for (i = 0; i < max; i++)
{
    unichar ch = [str characterAtIndex: i];
    nbytes[i] = (char) ch;
}
nbytes[i] = ‘\0′;

然后再将这段字节流按照正确的编码 (GB18030) 处理:

NSStringEncoding enc = CFStringConvertEncodingToNSStringEncoding(
    kCFStringEncodingGB_18030_2000);

NSLog(@"nstr: %@", [NSString stringWithCString: nbytes
                                      encoding: enc]);

结果果然得到了正确的输出:

2008-02-17 06:09:46.408 test[14095:10b] nstr: 图片_6.png

同样的逻辑可以用在很多类似的乱码情形中。完整的代码可以在这里下载: test-str-encoding.m

6 Frameworks Apple should include in their OS

To make developers’ lives easier, Apple really should make the following 6 frameworks bundled with their OS, it will make application distribution much better.

Now accepting donations

我在页面下方加入了两个小按钮,分别接受 RMB 和 USD 的捐赠,如果你对下面任一关键字有兴趣:

而且希望见到它们得到更好的开发,可以考虑给我一点鼓励,捐赠将用来购买书籍、设备和支付参加会议的费用 :) 不过不管有没有捐赠,我都只能按照自己的时间和计划来工作 (yep, 这和 vim 的 sponsor 政策差不多)。

Mac Dictionary Kit

开发了将近两周的一个项目终于可以称作正式发布了: Mac Dictionary Kit 的目标是成为一套在 Mac OS X 系统下常用的词典处理与转换工具。虽然在目前它只支持 stardict 格式的转换,但相信随着以后代码的抽象和新格式需求的增加,我们会支持更多的格式,以及更强大、更复杂的转换功能。也尽可能提供更方便的图形用户界面。

DictUnifier

目前 MDK 以两个子项目的形式发布: sdconv 是独立的命令行方式转换工具,专门用于 stardict 词典格式到 Dictionary 2.0 词典格式的转换;DictUnifier 是一个图形界面转换工具,它的设计是为了自动探测并支持多种来源格式,虽然目前它也只支持 stardict 格式。

发布在 Google Code 上,主要是为了提供更好的下载服务、更详细的文档和 bug 跟踪。因为这个项目原来参考了 stardict 的代码,所以沿用 GNU GPLv2 发布。

目前,这两套工具提供的都是 Universal Binary,支持 Intel 和 PowerPC 的 Mac 机器。也都是绿色软件,只需要解压/挂载就可以使用。

在这两套工具正式发布之前,得到了水木社区 Apple 版许多版友的测试与 bug 报告,在此表示诚挚的谢意。

Nally

这两天做了一些 Nally (MacBlueTelnet) 的修改,主要是让它支持 GBK 编码的 BBS。如果你经常在 Mac 下上 BBS,又对现有的客户端 (Terminal, iTerm, AlienBBS) 的显示正确性/速度/文本渲染效果不满意,可以试一试 yllan 开发的这个 Nally。最大的优点就是用 Core Text 渲染文本,速度特别快,而且字体选择方面专为中文 BBS 优化。我自己已经用它替代使用了很长时间的 iTerm 了。

另外,这是一个开放源码的项目,你也可以用 subversion checkout 下代码做出贡献哦。

Nally, 连线水木

The State of wine on OS X

wine 虽然声称自己是一个跨各平台的项目,可是它的 Mac OS X 支持一直很糟糕。因为最近 ie4osx 的出现,原本对 wine 不感兴趣的人,如我又开始活络起来。今天花了一点时间理了理 wine 在 OS X 上的几个问题,简单记录如下,也许对别人有用。

首先,wine 原本是不支持 Mac OS X 的,但独立的 Darwine 项目很大程度上解决了这一点,可是 Darwine 项目自从去年十月份一来就再也没有更新过了,也没有给 x86 的二进制包发布。最近 ie4osx 的编译者提供了二进制包的下载,其实是来自这里编译的,然而这里提供的安装包中能用只有 x11drv,也就是说,wine 必须依靠 X11 才能运行,不能脱离 X11,像一个 native 的 OS X 应用程序那样运行。

原本 quartzdrv 也是 Darwine 项目中的一个分支,目标就是实现 native 的 OS X 支持,可惜出师未捷身先死,quartzdrv 目前也不能和最新的 wine 代码很好的兼容了。然而事实上,依赖 wine 开发的 CrossOver Mac 这个项目早就完成了 quartzdrv 的改定,只不过这是一个商业项目,修改后的代码并没有公开,甚为遗憾。

所以一个好用的开源 quartzdrv 实现就成为了一直悬而未决的开发难题,现在 wine 的 git tree 中虽然有部分残余,其实是去年 Pierre d’Herbemont 发到 wine-patch 邮件列表的一系列 patch 中的第 1, 2 个,后续的 patch 一直未能合并进去,然而我咨询 Pierre 得到的回答是,他发的这一系列 patch 的功能仍然不能补足整个 quartzdrv 的需要。

我强烈建议有兴趣的同学,把这个开发的想法作为明年 Google Summer of Code 的项目。

话说回来,基于 X11 的实现在 Leopard 下会有一个 IE 页面频繁刷新的问题,通过更新 X11 到 2.1.0 版本可以解决。

对各平台文本渲染技术的一个简短的介绍

上周末参加 thossclub 的讨论,对各平台文本排版与渲染技术做了一个简短的介绍,这个介绍的 slides 可以在这里下载: text-rendering-tech.pdf (6.1 MB, PDF)

macports 在 Mac OS X 10.5 下编译 +universal 的一个问题

近来在开发一个小软件,需要分发程序链接了一些用 macports 安装的库,众所周知,用 macports 安装 universal binary 程序是通过 +universal 这个缺省 variant 实现的。在默认情况下,显然用户不愿意安装 universal 的,既然都在自己机器上编译了,去编译其他平台的二进制程序即浪费时间又浪费空间。所以默认这个 variant 是禁用的,可是如果你自己开发的 Universal Binary 应用要链接用 macports 安装的那些库时,就必须首先确保这些库是 universal binary。

可是在 10.5 下通过 +universal 之后,链接程序会遇到类似下面的错误:

$ sudo port install gettext +universal
...
--->  Building gettext with target all
Error: Target org.macports.build returned: ... returned error 2
...
gcc -dynamiclib  -o ... -L/opt/local/lib -lc  
-isysroot /Developer/SDKs/MacOSX10.4u.sdk -arch i386 
-arch ppc -arch i386 -arch ppc -Wl,-framework -Wl,CoreFoundation 
-install_name  /opt/local/lib/libintl.8.dylib 
-compatibility_version 9 -current_version 9.2
ld: library not found for -ldylib1.10.5.o
collect2: ld returned 1 exit status
...

这是什么原因呢?仔细看最关键的地方在于,macports 缺省使用了 -isysroot /Developer/SDKs/MacOSX10.4u.sdk -arch i386 -arch ppc 这个编译器参数来生成,可是在 Mac OS X 10.5 下,如果仅仅使用这个参数,系统仍然会以为你要编译的是 10.5 下运行的程序,所以最后会尝试链接 10.5 的 libc,问题是 10.5 的 libc 在 10.4 的 SDK 路径下当然找不到,于是就出错了。参考 Xcode-Users 上的讨论

怎么解决呢?也简单,加上 -mmacosx-version-min=10.4 这个编译参数就行了。不过对于 macports 来说,在哪儿加倒是一个问题,你可以针对每个 port,在 Portfile 里加上:

configure.universal_cflags-append  "-mmacosx-version-min=10.4"

也可以一劳永逸地通过修改 /opt/local/share/macports/Tcl/port1.0/portconfigure.tcl 脚本中的 default configure.universal_cflags 实现,这里是一个简单的 patch

这个 patch 已经发到了 macports 的 trac 上,希望能尽快在官方版本中得到修复。