一、在Unix/Linux上令JVM支持中文輸出
如果用戶使用的是UNIX的遠程服務器,就會遇到中文字體在圖像中輸出的問題,特別是由於許多管理員並不喜歡把主機的locale定為zh(因為意味著可能出亂碼或必須裝微形圖形終端象zhcon,但很多情況下這樣的條件並不具備)。大部分程序員的JAVA經驗苟限於JSP腳本程序,部分熟練的程序員大概開發過中間件、servlet、applet或在WINDOWS上運行的GUI程序。如果開發的jfreechart是使用WINDOWS作為主機運行的話,可以略過這一段,但如果使用的是UNIX類型的服務器的話,就常常遇到意想不到的中文顯示困難,甚至還未到輸出中文字體的階段,程序就報告 display異常錯誤。原因就在於,JAVA awt原來是針對(X)Windows GUI編寫的程序,它常常需要使用display 1:0的設置設定顯示方式,在服務器模式下(象JSP或servlet),根本就不會有XWindowns運行,這時就會在許多程序中引起can not got display setting to 0:0的錯誤,包括jfreechart。解決辦法是在JVM啟動中增加-DJava.awt.headless=true的設置。但這樣又帶來另一個問題,會令使用象frame.getImage()方法的代碼中引起headless Exception,導致程序中止,而使用ImageBuffer的程序就不會受到影響。
象jfreechart這樣基於java awt,java Swing, Java 2D API和程序應用到Linux/UNIX上,中文字體的輸出是一個必須解決的問題,否則連jfreereport都不能使用了。servlet也會碰到類似的問題,但解決方式顯得相對簡單,servlet package已經內置了解決辦法,一般情況下,在 servlet抬頭設兩句:
response.setContentType("text/Html;charset=UTF-8");request.setCharacterEncoding("UTF-8");
中文亂碼就不得存在。與簡單的JSp/servlet字符集轉換相比,這個問題要復雜得多,甚至比一般的Linux中文化還要復雜。在正常情況下, jre只包含少數幾種字體(Font),但可以從X 系統,象windows獲得喜歡的字體支持;因此,如果開發者和使用者是在中文Windows系統上開發,大概不會發覺問題的存在。但一旦當程序發布到 UNIX/Linux系統上後,就會發現圖形中的中文字符成為一個個的問號或者小方框。而此時,象JSP/servlet這樣的程序在客戶端的顯示卻是完全正常的。一般情況下,Java默認情況下是使用en_OTF-8,或者ISO_8859_1讀入字符串,因此,象JSP通常使用從8859_1強制轉型為gb3312/GBK,就可以正常顯示中文,但是在上述的情形下,這種強制轉型地是完全無效的。為什麼呢?如果程序員的系統概念是清晰的話,就會明白, JSP/SERVLET的字符串輸出,只是輸出字型,然後由客戶端(一般是浏覽器)在客戶端桌面重整字型,用的是客戶端的字體,而相反,JfreeChart這樣的圖形程序輸出的是一個圖像,用的是服務器端jre的字體,與XWindows的字體也無關。當系統本身不帶有字型時(font),這正是服務器所常見的,就只能向jre添加支持中文的字體(font)才能根本上解決這個問題。
Jre 的字體設置原理與Xwindows相似,並延用相同的工具,事實上,二進制字體文件延用相同的標准,各個公司間的字體集,象聯想、方正、微軟以及 Linux XWindows下是相同的,完全可以互相拷貝,僅僅是讀取字體的方式流程和設置方式稍有區別。目錄提及linux漢化的文章,其中主要就是增加中文字體的支持,很多是廢話連篇,不知其所以然亂撞一通後驚呼"搞定啦"這樣不可重復的形式。所以這裡先復習一下,然後和JRE的設置對照,原理就會顯得比較清晰。目前linux的字體有兩處使用,一是linux console下的字體,二是Xwin等應用程序的字體,包括象zhcon這樣的偽console程序。每處應用字體的程序都可以有自已的字體設置目錄;但隨著Linux集成程度的強化,都傾向向通過默認的unixsocket7000端口調用xfs的字體服務。因此,字體設置只需對xfs進行設置就可以完成。一些文章聲稱要停掉XFS,實際上毫無必要;xfs的調用僅僅是作為一個在XFConfig中的FontPath選項,作為另一個添加字體的方法,就是直接把包含字體的目錄添加到FontPath,然後手工執行ttmkfdir——恰恰這本來是xfs設計代替您去做的。用戶實際上需要做的,要麼是直接在圖形工具中把字體文件添加到fonts:\\\中,或者是手工把字體文件加到xfs的目錄下的對應locale的目錄中,一般是在 /usr/share/lib/fonts/zh_CN/TrueType,重啟xfs就搞定了。作為手工添加到XFConfig中的目錄,在XWin 中,簡單地說,字體位圖文件是通用的,包括對JRE,放在某一個目錄中,用戶需要做的就是通知XWIN這些目錄在什麼地方,設置的位置就在 /etc/X11/XConfig的FontPath項。運行ttmkfdir命令生成fonts.dir文件,實際上都是字體調用的對照表,另外用戶可以編輯fonts.alias這樣的文件,目的就是讓字體有個易記的名字。因此,字體的安裝關鍵在於字體位圖文件(拷到某個目錄),對照文件(由 ttmkfdir命令生成),和字體別名設置,所不同的是,在Xwin中這些由xfs自動完成,在jre中,就要開發者自已手工完成。
就Jre 而言,字體位圖目錄是固定的,在$JRE_HOME/lib/fonts目錄中;fonts.dir*的目錄對照表文件也是一樣的,同樣是由 ttmkdir程序生成,而相當於別名等設置的文件,集中在$JRE_HOME/lib目錄下的*font.properties*"文件中定義。如果 JVM能直接支持中文輸出,那麼就要求*font.propertIEs*屬性文件中指示的字體型本身是支持中文的(換言之,JSDK自帶的字體文件是不支持中文的)。按http://Java.sun.com/J2SE/1.3/docs/guide/intl/fontprop.Html的說明,JVM按以下順序搜索字體屬性文件,尖括號是JVM檢測的系統屬性:
font.propertIEs.
但在大多數情況下,實際上只需要面向一個font.properties文件。重新編一個font.properties文件是一項艱苦的工程,幸好在 Linux中有一個font.properties.zh.Turbo,本來是面向TurboLinux用戶,不過在大多數情況下可以基於它修改。把這個文件重命名為font.properties,覆蓋掉原來的文件,但系統這時仍不支持中文,查看一下,就會發現 font.propertIEs.zh.Turbo文件中的"-tlc-song-medium-r-normal--*-%d-*-*-c-*-gbk -0"字型在fonts.dir對照表中並不具備,這種字型包含在TurboLinux的系統字型庫中。下面的方法有兩個,一是安裝這種字體,二是更改另一種字體型庫並重新指定。TurboLinux的字體安裝文件名字是ttf-zh-song*rpm,在互聯網上可以找到,安裝後把 /usr/lib/X11/fonts/tt下的ttf文件拷貝到$JRE_HOME/lib/fonts目錄下,重新生成fonts.dir文件。第二種辦法就是重找字體庫,微軟WINDOWS上的fonts目錄下的ttf文件一般可用,但更全的是從http://www.microsoft.com/china/Windows2000/downloads/18030.ASP 下載它的字符集文件,安裝後把ttf拷到JRE的fonts目錄下;另外, XWin如果支持中文的話,可以從/usr/lib/X11/fonts/TrueType下找到一兩個支持中文的字體文件。
把這些文件統統拷到JRE的fonts目錄並不能令JVM立刻支持中文,回想一下前面提到的,在font.properties中指定的文字類型,必須有一個對照表fonts.dir指示JVM如何把用戶調用的font類型匹配到相應的字型文件上。因此,運行ttmkfdir > fonts.dir生成新的對照表。用Vi打開這個文件,最上面的數字是系統可以調用的字型數目,下面的屬性值對左側就是物理字型名稱,右側是它的編號,這就是用到font.properties 文件中指明使用的編號(包含了設置,左側的就是字符的別名,即虛擬字型),區別僅僅是把0-0-0c-0這類設置中的某幾項改作通配符和%d接受調用參數而已,不改也行,大不了輸出的字難看一點(反正我不是美工,不太關心)。用可用的字型編號代替了font.propertIEs中無效的字型設置後,理論上似乎JVM已經支持中文了,但在實際操作上,仍是經常見到問號、空格之類,原因就在於Java對中文的支持不但與運行環境有關,還與編譯參數有關,如果類文件不是以gb2312/encoding編譯的話,等同於讀入是OTF-8/8859_1,這時再轉換也沒有用了,因此,如果是drawString 之類的,必須切記使用(-encoding gb2312);當然,如果操作系統本身已經是中文的話,這條就由編譯器自動采納了。