Nov 23

首先要说明的是,sunpinyin-2.0 for Mac版本还在开发中,目前的状况是,可以build并安装到系统中,并且能够进行输入,不过用户配置方面(从界面到代码)都还没有实现。

第一步,需要安装分布式版本管理工具mercurial,并安装MacPorts,以安装必要的autotools软件包:

$ sudo /opt/local/bin/port install glib2 intltool sqlite3
$ export PATH=$PATH:/Developer/usr/bin:/opt/local/bin
(为了将来方便,可将此行加入到~/.bash_profile中)

接下来,从OpenSolaris上check out项目的代码,这个会花费比较长的时间,需要耐心 :)

$ hg clone ssh://anon@hg.opensolaris.org/hg/nv-g11n/inputmethod

然后进入到sunpinyin2目录中,执行autogen.sh

$ cd sunpinyin2
$ cd data; ln -s ../../sunpinyin/ime/data/lm_sc.t3g.le lm_sc.t3g; cd -
$ ACLOCAL_FLAGS=-I/opt/local/share/aclocal ./autogen.sh \
  --disable-cle --disable-ibus

由于目前在autogen.sh里,hardcoded了--enable-ibus等选项,所以在执行configure时会出错,所以需要再用适当的选项重新执行一遍configure;另外需要使用MacPorts的glibtoolize重新生成一下libtool,否则无法进行链接,

$ /opt/local/bin/glibtoolize --force --copy
$ ./configure --enable-debug --disable-cle --disable-ibus

接下来需要build词表,

$ cd build; make genpyt; make lexicon; cd -

最后就是build基于IMKit的输入法前端了,

$ cd wrapper/macos
$ make clean; make
$ sudo make install

现在,sunpinyin-2.0应该已经安装到系统中了,需要re-login才可以看到这个输入法。

调试的时候,需要用ssh从另外一台机器远程登录到本机上。之后,如果要验证bug fixes,为了避免重复re-login,可以反复执行killall -9 SunPinyin,直至系统报告没有SunPinyin进程,然后也要重新启动用来测试的应用(例如textedit)。

Tagged with:
Nov 10

ibus-sunpinyin 2.0 发布 RC2,详情请参见http://groups.google.com/group/sunpinyin-developers/… 另外,linuxtoy发了一篇关于如何在fedora上安装ibus-sunpinyin的文档,希望有朋友能帮忙build一个rpm包,我们可以放到google-group讨论组、以及项目网站上,方便大家下载安装。

总体的状况是,ibus-sunpinyin2的发展形势很喜人啊 :)

Tagged with:
Nov 08

从朋友那里得知,ibus-sunpinyin2已经进入gentoo官方库,感谢整个开发团队(特别是Kov),以及所有支持和关心我们的朋友们!:)

Tagged with:
Nov 04

感谢Kov Chai的超辛苦付出,以及William XueLeo Zheng等同学们的共同努力,ibus-sunpinyin 2.0发布了第一个release candidate!详情请参见:

http://groups.google.com/group/sunpinyin-developers/…

Tagged with:
Sep 28

shuttle_SG33G5和很多第一批的Time Capsule用户一样,18月过后,我的TC-1TB死了,估计是电容击穿了。拿到苹果维修中心,人家说只能采用调换维修的方式,因为过保,要收费4,800元,而买一台全新的才2,300多。回到家,把TC拆了,将硬盘取出来,放到刚买的一个USB外置硬盘盒里,硬盘盒居然点不亮,心里一下就毛了,直想骂娘。要知道,我给儿子拍的照片和视频,还有积攒的很多电子书和资料,可都在里面呢。极其后悔,当初没把重要的数据刻盘备份。

第二天早起去鼎好,发现是店家给我的电源线有问题。插在Windows上,发现分区格式不支持。又一阵紧张,万一USB硬盘盒不能支持EFI分区,难不成我得找一台Mac Pro?在鼎好找了家苹果专卖店,将USB硬盘盒接到MBP上,结果能认出来,总算放心了。Time-Capsule是不敢买了,和老婆商量了一下,不如搞个准系统,用opensolaris/zfs自己攒一台Home NAS。在网上搜到了一家“北京准系统大本营”的店家,上周末和朋友约好一起去转转。

我起先买的是AOpen S180机箱(最多可放3块3.5寸的硬盘),加翔升的Atom 330套板(支持4个SATA-II接口)。回到家发现,CPU的风扇和机箱的风扇噪音比较大。问了下朋友,他买的浩鑫的SG33G5整机噪音比较小。不过价钱也贵出一大节,机箱/电源+主板就要1,950。和老婆合计了一下,咬了咬牙,换!第二天去E世界,换了SG33G5,上的是E1500双核赛扬处理器(330元)。

回到家,发现板载的Marvell网卡,solaris缺省不支持,需要第三方驱动。安装好了之后,以为总算是大功告成了。不过发现,Leopard上的Finder访问NFS巨慢无比。绝望地以为,是网卡驱动支持得不好。后来发现,从solaris上scp,速度还是很快的,google了一下,发现是Mac OS的固疾,再次鄙视Apple一下!最后花了一晚上,把TC硬盘上的东西,scp到solaris上。下一步,是要给rpool做mirror,被吓怕了。貌似有些麻烦,放假时再整了 …

最近去村里买东西,都很不顺利,总要跑两遍,郁闷 …

Tagged with:
Sep 12

我们刚刚为SunPinyin项目建立了一个google-group。虽然oso-inputmethod项目也有一个mail-list,不过是和其他opensolaris i18n/l10n项目share的。而且似乎国内的开发者或用户,还是更习惯和倾向使用google-group作为交流的途径。因此我们就创建了这个group。

SunPinyin的开发者会使用这个mail group来讨论开发相关的问题,同时用户也可以用这个mail group来报告发现的问题。(觉得再建一个sunpinyin-users的group还不是这么紧迫和必要。)大家也可以直接发邮件到sunpinyin-developers@googlegroups.com

期待您的加入!

Tagged with:
Sep 07

sunpinyin_event_flow

顺便再补充一句,为了将来能让ime-core编译为一个shared object,SunPinyin2开始使用自己的key mapping。这个文件列出了一些特殊键(function key),它们的keycode和X11 keysym是一致的,因此和scim和ibus的keysym也是一致的。其他的键,例如a-z/A-Z/0-9等和ASCII兼容的,可以直接传入(其实这和X11的keysym还是一致的)。再以外的keycode就不处理直接返回了。因此各位在给SunPinyin2做porting时,还需要编写一个简单的键盘事件的影射。

Tagged with:
Sep 06

SunPinyin2在设计和实现的过程中,希望能同时支持简体、繁体的词典及语言模型,支持不同的拼音方案,支持不同的输入风格(包括微软拼音和类似sogou或google拼音)。不同的输入风格,对应了不同的view的实现。(虽然目前SunPinyin2还没有重新实现微软拼音的输入风格——CIMIModernView。)

这些选项组合起来数目繁多,不过它们都是正交的,正好可以让我们使用policy based的实现方式。

我们定义了一个CSunpinyinProfile的模板类,它继承了ISunpinyinProfile这个公共接口类(否则我无法声明一个保存其指针的container),和它的三个模板参数,LanguagePolicy,PinyinSchemePolicy,InputStylePolicy。它的createProfile方法,调用policy类中的相关方法,来创建具体的一个view实例。为了避免创建policy的实例,并且更重要的是要共享policy的公共数据,policy类都是单件(singleton)。

LanguagePolicy负责初始化词典、用户词典和语言模型,以及根据这些资源创建CIMIContext类的实例。CSimplifiedChinesePolicy是一个具体的实现。另外还有一些方法,可以让你设置CIMIContext类实例的缺省属性。例如,enableFullPunctenableFullSymbolsetPunctMapping方法设置s_getFullPunctOp(被所有由该policy类创建的CIMIContext实例所共享)的标点符号影射。我们之前总结为shared & global的用户配置项。可以设想,用户在设置对话框自行定义标点符号的映射关系后,程序员只需要调用CSimplifiedChinesePolicy::setPunctMapping。这个配置项的更改会应用到所有现有的输入上下文(或会话),对输入上下文来说是透明的。

PinyinSchemePolicy负责创建拼音切分器的实例。CQuanpinSchemePolicyCShuangpinSchemePolicy是两个具体的实现。以CQuanpinSchemePolicy类为例,createPySegmentor方法创建了CQuanpinSegmentor的一个实例并返回。和CSimplifiedChinesePolicy类似,CQuanpinSchemePolicy也有一些shared & global的配置项,例如是否支持易混淆音和自动纠错。

InputStylePolicy负责创建CIMIView的实例。CClassicStylePolicy是一个具体的实现。它只是简单的创建一个CIMIClassicView的实例并返回。CSunPinyinProfile<…>::createProfile方法虽然也是返回一个view的实例,不过这个view是已经设置停当、可以工作的view。

CSunpinyinSessionFactory是一个singleton的工厂类,其createSession方法根据factory中policy的设定,调用某个CSunpinyinProfile实例的createProfile方法,创建一个具体的view。要添加新的语言支持或拼音方案,不仅要完成自己的那部分coding,还要记得编写相应的policy classes,并将支持的所有组合注册到CSunpinyinSessionFactory中(没办法,在这个地方就只能穷举了)。

因为我们最后expose给外部的是一个view的实例,所以如果用户的配置涉及到对policy的切换(我们之前总结为non-shared but global的配置项),以前创建的输入上下文(即view),就面临一个抉择。要么保持不变,继续使用其目前的输入风格;要么要重新创建,discard掉以前上下文的状态信息(例如preedit/candidates)。通常,我们对某个平台的移植,都是创建一个包含view的会话类,这个会话类适配该平台的接口,并将输入事件filter给view。那么我们的这些会话类是如何知道发生了non-shared but global选项的改变呢?

CSunpinyinSessionFactory类提供了一个简单的方法,updateToken/getToken。当平台相关的会话类调用CSunpinyinSessionFactory类创建一个view的实例,同时应该调用getToken得到一个token(口令)。当会话类得到焦点的时候,它可以调用getToken再次得到一个token,比较与自己持有的这个token是否一致。如果不一致,就表明发生了non-shared but global选项的更改。与此相对应地,如果用户对non-shared but global的选项发生了修改,程序员应该要调用updateToken去更新factory当前的token。

这个方式有些丑陋,稍微改观一点的方法可能是,我们再加入一个抽象层次,例如CSunpinyinSession,然后它用signal/slot的方式connect到factory的reset_session信号。另一个问题,配置项还是比较分散,如果要编写一个集中的CSunpinyinOption类的话,就会有很多duplicated的代码。我对上面这些问题,还没有想得很清楚,也非常欢迎大家多提宝贵意见!:)

行文至此,我的“What’s new in SunPinyin2”系列也就暂时告一段落了。希望对William、以及SunPinyin2的移植工作有所帮助。我还会继续竭尽全力,投入到SunPinyin2的开发和移植工作中 :)

Tagged with:
Sep 05

原先的SunPinyin没有用户词典,只是通过User History Cache纪录了用户近期输入的bi-gram信息。如果一个bi-gram,其概率比系统词典的某个uni-gram要低,这个bi-gram的组合就不会出现在用户的候选列表中。例如,虽然用户频繁的输入钥匙,但是它依然很难出现在候选列表中,因为“要是”这个unigram的概率要更高。如果“钥匙”出现在候选中,一定是第一候选,因为它是作为一个最佳候选句子来呈现给用户的。如果用户选择了“要是”,那么“钥匙”又不知道要过多久才能出现了。这个缺陷也是广受大家诟病的地方。其实,SunPinyin原先的架构是支持将用户自定义词放到lattice上进行搜索的

我们在SunPinyin2中,通过sqlite3来实现用户词典,目前我们的用户词典支持记录<=6个的字符。

CUserDict::_createTable/_createIndexes:

这两个私有方法尝试创建一个table和index。这个表的结构是,首先词的ID(从1开始的)和长度,接下来是6个声母,然后是6个韵母,再其次是6个声调。然后我们为长度+6个声母建立了一个索引。sqlite的query对搜索条件和index有一些限制,要求条件的次序和index的次序相同,并且一旦某个条件是非相等性的(例如>),则该条件及其之后的条件都无法用index来加速。另外,每个from子句只能使用一个index。

因为我们经常会有不完整拼音的查询,或者换句话说用户经常会输入不完整拼音,所以table和index就被设计成了这个样子。

CUserDict::addWord (syllables, word):

使用了sqlite中的prepared statement来进行插入。最后,在插入成功的情况下,调用sqlite3_last_insert_rowid得到上一次插入记录的row id,加上一个offset并返回。如果失败,就返回0。

CUserDict::removeWord (wid):

这个相对简单,按照wid将记录从数据库中删除。

CUserDict::getWords (syllables, result):

这个方法将syllables在数据库中对应的记录,放到result中。我们返回的词id类型,和系统词典的保持一致,都是CPinyinTrie::TWordIdInfo。这里再解释一下,用户词典的最大容量为什么是6万多个。这个就是由于CPinyinTrie::TWordIdInfo的m_id的长度决定的,是2^18-1。

我们按照sqlite的限制,小心翼翼地组织好查询的where子句,查询,然后迭代结果,填充results,最后返回。

CUserDict::operator [] (wid):

这个方法也相对简单,就是返回wid对应的字符串。因为sqlite只支持utf8和utf16,而且utf16还要使用一套不同的接口,所以我们还是使用utf8作为数据库内的字符串编码。而sunpinyin-ime要求的是ucs-4编码的字符串,所以还要进行一个编码转换。同时把(wid, wstr)这个pair加入到m_dict中。wstr是动态分配的,由m_dict在析构时释放。这里还利用了这样一个事实,std::basic_string是COW的。我们在m_dict中保存的其实是wstr的拷贝,不过这个拷贝和wstr共享同一个C string的buffer。

接下来,我会介绍SunPinyin2中另一个比较复杂和tricky的部分——用户配置(imi_options.h),这一部分到目前还没有完全定型,非常欢迎大家多提宝贵意见。

Tagged with:
Aug 31

原先SunPinyin是使用CBone/CSkeleton来组织search lattice的,参见sunpinyin代码导读(九)。每一个Bone对应一个syllable(或者严格的说,是一个segment),而CSkeleton是CBone的双向链表(std::list)。这个结构不太适合对模糊切分的支持。因此在SunPinyin2中,lattice不再以syllable为单位了,而是以单个的拼音字符为单位。如下图所示:

sunpinyin2_lattice_search

我们定义了一个CLattice的类,表示整个的search lattice,其对应于原来的CSkeleton。将每个column称为一个CLatticeFrame,对应于原来的CBone/CBoneInnerData。而TLexiconStateTLatticeState和以前的差别不大,只是把位置信息从iterator换成了index。另外为了支持用户词典,在TLexiconState加入了一些相应的字段,例如m_wordsm_syls

CIMIContext类的主要入口是buildLattice (segments, rebuildFrom, doSearch)。rebuildFrom这个参数值其实就是IPySegmentor::updatedFrom() + 1,因为在lattice上,拼音字符串是从index 1开始的,而在切分器中是从0开始的。buildLattice遍历segments(由IPySegmentor::getSegments所返回的),跳过rebuildFrom之前的那些segments,然后调用_forwardXXX方法,build每个目标frame上的lexiconStates。直到segments的结束或者超过一定长度,调用_forwardTail处理结尾。然后,在doSearch为true的情况下,调用searchFrom进行搜索。

CIMIContext类中的_forwardXXX方法,和原来的forwardXXX方法大同小异。_forwardSingleSyllable中对用户词典做了一些额外的处理。因为我们现在的用户词典是基于sqlite实现的,不是一棵trie树,所以和基于trie树的系统词典处理起来有所不同。例如,用户词典中有“测试拼音”这个词,当用户输入到ceshipin的时候,从数据库中得到的词的列表是空的,且对系统词典(CPinyinTrie)的transfer也会返回一个空节点。但这种情况下,我们不能drop掉ceshipin这个音节序列,还是要把它加入到目标latticeFrame的lexiconStates中

CIMIContext::searchFrom也和前作基本相同,都是通过frame上的lexiconStates,调用_transferBetween方法来构建latticeStates,同时调用语言模型进行句子的评分。最后调用_backTraceBestPath,把最佳路径标识出来。稍有不同的是,searchFrom对fuzzyForwarding进行了一些处理,收窄了易混淆音节的搜索范围并降低了其初始的评分

其他的方法,例如_transferBetween, getBestSentence, getCandidates都和之前的实现大体相同。cancelSelectionmakeSelection得到了简化。memorize方法加入了将<=6个字符的短句(其中含有用户选择的),作为用户词条保存的功能。

另外CIMIContext有两个助手类,CGetFullPunctOpCGetFullSymbolOp,用来处理标点符号和字符的全角转换。这两个类也是可配置的,并且CIMIContext的所有实例都share同一份拷贝。因此,也属于shared & global的配置选项。

新版本的CIMIContext,在将拼音切分剥离之后,其职责更加明确和清晰;采用新的数据结构之后,逻辑也更简单和直观。代码行数也由原来的1.5k下降到0.6k;同时可配置性也得到了提升。

下一篇我会介绍一下用户词典的实现。

Tagged with:
preload preload preload