qte-从ttf字库中提取指定汉字生成qpf字库的笨办法
作者:刘志文  2007/4/23  Email:lzwwiner@163.com
这篇文档大家可以随意转载.我本人只保留所有权.转载请注明作者和出处.
解决思路: 获取汉字的unicode编码,使用unicode编码来获取ttf字库中的汉字来生成qpf字库.
首先对我所用的linux环境作以下说明:
Linux系统:    
    Redhat Linux 9
QT:            
    qt-embedded-free-3.3.3.tar.bz2
下面正式开始了:
1. 在根目录下新建一个目录
   mkdir qte
2. 把qt-embedded-free-3.3.3.tar.bz2移到qte目录
   mv qt-embedded-free-3.3.3.tar.bz2  /qte
3. 解压qt-embedded-free-3.3.3.tar.bz2 
   tar -jxvf qt-embedded-free-3.3.3.tar.bz2   
4. 重命名qt-embedded-free-3.3.3.tar.bz2文件夹
   mv qt-embedded-free-3.3.3 qte
5. 在根目录的qte目录下写如下shell脚本并保存成buildqte
   #!/bin/sh
   cd qte
   export QTDIR=$PWD
   export PATH=$QTDIR/bin:$PATH
   export LD_LIBRARY_PATH=$QTDIR/lib:$LD_LIBRARY_PATH
   ./configure -qt-gfx-transformed -qvfb
   make -C src  #这样只是编译src目录下的源码 如果你没有 moc uic 工具就全部编译  你可以直接make而不要后面的 -C src
   cd ..
6. 执行buildqte脚本编译x86版本的qte库.
   ./buildqte
7. 检查/qte/qte/bin目录是否有以下工具 qmake moc
   如果有就行了,没有就请仔细看第5步,或许你的机器已经安装了qt3 x11版本,可以把uic她拷贝到/qte/qte/bin下使用.
8. 修改/qte/qte/src/kernel/qmemorymamager_qws.cpp文件。(修改的代码很少,这也是最关键的部分。)
   说明:为什么要改这个文件
   请进入如下目录 /qte/qte/tools/makeqpf/ 打开main.cpp文件(这是makeqpf工具的源码)
   请关注下面的代码   
   129 class MakeQPF : public QMainWindow
   130 {
   131     Q_OBJECT
   132         QListView* view;
   133     public:
   134     MakeQPF()
   135     {
   136         view = new QListView(this);
   137         view->addColumn("Family");
   138         view->addColumn("Size");
   139         view->addColumn("Weight");
   140         view->addColumn("Style");
   141         setCentralWidget(view);
   142         QString fontdir = qws_topdir() + "/lib/fonts";
   143         readFontDir(fontdir);
   144
   145         connect(view,SIGNAL(selectionChanged(QListViewItem*)),this,SLOT(renderAndSave(QListViewItem*)));
   146     }
    重点关注145行,在这里调用了renderAndSave(QListViewItem*)槽
    234
    235     private slots:
    236         void renderAndSave(QListViewItem* i)
    237         {
    238             ((FontViewItem*)i)->renderAndSave();
    239         }
    重点关注238行
    54     void renderAndSave()
    55     {
    56         font = QFont(family,pointSize,weight,italic);
    57 #ifdef Q_WS_QWS
    58         memorymanager->savePrerenderedFont((QMemoryManager::FontID)font.handle());
    59 #endif
    60         setHeight(QFontMetrics(font).lineSpacing());
    61         repaint();
    62     }    
    重点关注58行
    33 #ifdef Q_WS_QWS
    34 #include <qmemorymanager_qws.h>
    35 #endif     
    重点关注34行    
    58行中的memorymanager是在头文件qmemorymanager_qws.h中定义的。
    打开/qte/qte/include/qmemorymanager_qws.h文件
    77 #ifndef QT_NO_QWS_SAVEFONTS
    78     void savePrerenderedFont(const QFontDef&, bool all=TRUE);
    79     void savePrerenderedFont(FontID id, bool all=TRUE);
    80 #endif
    81     bool fontSmooth(FontID id) const;
    82     int fontAscent(FontID id) const;
    83     int fontDescent(FontID id) const;
    84     int fontMinLeftBearing(FontID id) const;
    85     int fontMinRightBearing(FontID id) const;
    86     int fontLeading(FontID id) const;
    87     int fontMaxWidth(FontID id) const;
    88     int fontUnderlinePos(FontID id) const;
    89     int fontLineWidth(FontID id) const;
    90     int fontLineSpacing(FontID id) const;
    91
    92 private:
    93     QMap<PixmapID,QMemoryManagerPixmap> pixmap_map;
    94     int next_pixmap_id;
    95     QMap<QString,FontID> font_map;
    96     int next_font_id;
    97 };
    98
    99 extern QMemoryManager* memorymanager;
    重点关注79行和99行 
    79行的成员函数实现在/qte/qte/src/kernel/qmemorymanager_qws.cpp   
    打开上面的文件看到下面的代码( 生成qpf字库的工作就在下面做了, 下面是没有改动过的代码 ) 
    991 #ifndef QT_NO_QWS_SAVEFONTS
    992 void QMemoryManager::savePrerenderedFont(const QFontDef& f, bool all)
    993 {
    994     QMemoryManagerFont* mmf = (QMemoryManagerFont*)refFont(f);
    995     savePrerenderedFont((FontID)mmf,all);
    996 }
    997
    998 void QMemoryManager::savePrerenderedFont(FontID id, bool all)
    999 {
   1000     QMemoryManagerFont* mmf = (QMemoryManagerFont*)id;
   1001
   1002     if ( !mmf->renderer ) {
   1003     qWarning("Already a ROM font");
   1004     } else {
   1005     if ( !mmf->tree )
   1006         mmf->tree = new QGlyphTree(32,32,mmf->renderer); // 32 = " " - likely to be in the font
   1007     if ( all ) {
   1008         int j=0;
   1009         //qDebug("Rendering %s",fontFilename(mmf->def).ascii());
   1010         for (int i=0; i<=mmf->renderer->maxchar; i++) {
   1011         if ( mmf->renderer->inFont( i ) ) {
   1012             mmf->tree->get( i, mmf->renderer );
   1013             if ( !(j++ & 0x3f)  ) {
   1014             // XXX keep it from becoming degenerate - should be in QGlyphTree
   1015             mmf->tree->compress();
   1016             QGlyphTree::balance(mmf->tree);
   1017             }
   1018         }
   1019         }
   1020     }
   1021     mmf->tree->compress();
   1022     QGlyphTree::balance(mmf->tree);
   1023     //qDebug("DUMP..."); mmf->tree->dump();
   1024     QFile f(fontFilename(mmf->def));
   1025     f.open(IO_WriteOnly);
   1026     f.writeBlock((char*)&mmf->fm,sizeof(mmf->fm));
   1027     mmf->tree->write(f);
   1028     }
   1029 }
   1030 #endif  
    重点关注1010行到1019行
    里面中的for循环的i变量就是unicode编码对应。    
    如果你只是要大小写字母,你可以将for循环改成下面这样    
    for (int i=0x21; i<=0x7e; i++) //大小写字母和英文标点符号uincode编码是从0x21 到 0x7e
    {
        if ( mmf->renderer->inFont( i ) )
        {
            mmf->tree->get( i, mmf->renderer );
            if ( !(j++ & 0x3f)  )
            {
                // XXX keep it from becoming degenerate - should be in QGlyphTree
                mmf->tree->compress();
                QGlyphTree::balance(mmf->tree);
            }
        }
   }   
   上面的循环就是生成只包含大小写字母和英文标点符号的qpf字库了。   
    现在我们的目标是生成指定汉字的qpf字库.
    现在就以我修改的代码为例进行说明。    
    把qmemorymanager_qws.cpp中998-1029行中 void QMemoryManager::savePrerenderedFont(FontID id, bool all)    
    实现代码修改成如下代码:
    void QMemoryManager::savePrerenderedFont(FontID id, bool all)
    {
        QMemoryManagerFont* mmf = (QMemoryManagerFont*)id;  
        if ( !mmf->renderer )
        {
            qWarning("Already a ROM font");
        }
        else
        {
            if ( !mmf->tree )
                mmf->tree = new QGlyphTree(32,32,mmf->renderer); // 32 = " " - likely to be in the font          
            if ( all )
            {
                int j=0;
                qDebug("Rendering %s",fontFilename(mmf->def).ascii());
                //for (int i=0; i<=mmf->renderer->maxchar; i++) {
                printf("mmf->renderer->maxchar=%u\n", mmf->renderer->maxchar);  
                for (int i=0x21; i<=0x7e; i++)//大小写字母和英文标点符号uincode编码是从0x21 到 0x7e(这是一般字库都要有的)
                {
                    if ( mmf->renderer->inFont( i ) )
                    {
                        mmf->tree->get( i, mmf->renderer );
                        if ( !(j++ & 0x3f)  )
                        {
                            // XXX keep it from becoming degenerate - should be in QGlyphTree
                            mmf->tree->compress();
                            QGlyphTree::balance(mmf->tree);
                        }
                    }
                }
                //把你需要的汉字加入到 tmp 中去,这样一来就可以实现从ttf中提取指定汉字的qpf字库了.
                //本人的方法笨就笨在这里,每次添加了汉字就要重新编译qte库和makeqpf
                QString tmp("开始停止登录退出参数设置户代码用户密码提示信息你输设置图像摄像图片预览图片浏览摄像状态信息设备状态通道检测");
                 //注意现在只能处理utf-8的汉字编码,    我是由汉字的utf-8编码来获取汉字unicode编码 关于utf-8, 参考utf-8的相关信息
                unsigned int len = tmp.length();
                const char *ch = tmp.latin1();
                unsigned int n;
                unsigned int count = 0; 
                printf( "len=%u\n",len ); 
                for( n=0;  n<len ; )
                {
                    if( ( (unsigned char)ch[n] ) >> 4 == 0xE)//0xE开始是汉字 汉字3字节
                    {
                        unsigned int sp1=0;
                        unsigned int sp2=0;
                        unsigned int sp3=0;
                        //以下是utf-8 转换成 unicode编码的过程
                        //分别获取三个字节,分别转换,然后三个相加就是所需的汉字unicode编码
                        sp1 =(unsigned int)( (unsigned char)ch[n] - 0xe0 );
                        sp2 =(unsigned int)( (unsigned char)ch[n+1] - 0x80 );
                        sp3 =(unsigned int)( (unsigned char)ch[n+2] - 0x80 );
                        //printf( "0x%x 0x%x 0x%x \n", sp1, sp2, sp3 );
                        sp1 = sp1 << 12;
                        sp2 = sp2 << 6;
                        sp3 = sp1 + sp2 + sp3;
                        //printf( "0x%x\n\n", sp3 );
                        if ( mmf->renderer->inFont( sp3 ) )
                        {
                            mmf->tree->get( sp3, mmf->renderer );
                            if ( !(j++ & 0x3f)  )
                            {
                                // XXX keep it from becoming degenerate - should be in QGlyphTree
                                mmf->tree->compress();
                                QGlyphTree::balance(mmf->tree);
                            }
                        }
                        n += 3;
                        count++;
                    }
                    else if ( ( (unsigned char)ch[n] ) >> 7 == 0x0 )//单字节
                    {
                         n ++;
                    }
                    else if( ( (unsigned char)ch[n]) >> 5 == 0x6 )//双字节
                    {
                        n += 2;
                    }
                 }
             }
             mmf->tree->compress();
             QGlyphTree::balance(mmf->tree);
             //qDebug("DUMP..."); mmf->tree->dump();
             QFile f(fontFilename(mmf->def));
             f.open(IO_WriteOnly);
             f.writeBlock((char*)&mmf->fm,sizeof(mmf->fm));
             mmf->tree->write(f);
        }
    }
    以上就是修改代码的过程
9.修改buildqte shell脚本.     
    把buildqte脚本中的下面一行注释掉
    ./configure -qt-gfx-transformed -qvfb
    ./buildqte 重新编译qte库
10.编译makeqpf工具(qte用这个工具来生成qpf字库).
    注意:makeqpf一定要编译成qte的版本,不能编译成x11版本
    cd /qte/qte/tools/makeqpf
    ../../bin/qmake -project
    ../../bin/qmake 
    make
    (请按上面的命令执行)  
11. 修该fontdir文件
    fontdir文件一般默认在/usr/lib/qt-3.1/lib/fonts/目录下
    具体修改,应该大家都清楚怎么做了.       
    下面是我的fontdir文件的内容
    unifont     unifont_160_50.qpf          QPF n 50 160 su
    #simfang    simfang.bdf                BDF n 50 140 su
    #simfang    simfang.ttf                 FT  n 50 140 su
    #simfnag    simfang.pcf                PCF n 50 140 su
    #simfang      simfang_140_50.qpf      QPF n 50 140 su
    #simfang     simfang.ttf               FT     n 50 120 su
    #simfang    simhei_120_50.qpf         QPF n 50 120 su
    #simhei     simhei.ttf                 FT     n 50 140 su
    #simhei     simhei_140_50.qpf         QPF n 50 140 su
    #simkai     simkai.ttf                 FT     n 50 130 su
    #simkai     simkai_130_50.qpf         QPF n 50 130 su
    #
    zysong zysong.ttf          FT n 50 120 su
    zysong zysong_120_50.qpf  QPF n 50 120 su
12. 最后就是运行makeqpf生成qpf字库
    makeqpf可以在framebuffer中运行,也可以在qvfb中运行.
    运行后你可以去看看自己的成果了,字库是不是小了.
后记:
    字库提取的问题困扰了很久,我只用几百个汉字,但是每次生成的字库很大,对嵌入式来说存储空间是很不划算的.
    现在我的办法有点笨,但是总算能解决这个问题了,所以第一时间给大家共享出来,希望也能帮上你的忙.
    也希望大家提出建议和指出错误,如果有有心人,还希望能够在完善这个过程.(因为本人的办法确实还是比较麻烦.)
    这篇文档大家可以随意转载.我本人只保留所有权.转载请注明作者和出处.
    2007/04/22
    刘志文    
lzwwiner@163.com[ 此贴被lzwwiner在2007-05-08 14:24重新编辑 ]