在本系列的第 1 部分中,我們通過構建了一個終端模擬器來探討了 MIDP 對于網絡化應用程序的潛力。第 2 部分重點介紹了構建一個好的用戶界面來使 MIDTerm 成為一個良好交互的應用程序。在第 3 部分中,我們將通過利用 MIDP 的呈現自定義字體的功能來增加一些修飾和對外觀的一些急需的控制。
首先我們討論對字體的內建支持的優勢和限制,并且確定哪種應用程序可能需要自定義字體。然后介紹一下實現您自己的字體的技巧,并將其正確地應用于終端模擬器 MIDlet 中。
內置字體
使用 MIDP 的 Graphics
類,可以調用其 setFont()
來指定字體,然后調用該字體的一種 drawChar()
或 drawString()
方法來在 canvas 或后臺圖像上呈現字體。MIDP 僅提供了一套有限的字體選項,但是:字體可以是等寬的或成比例的,大小可以是小、中或大,并且樣式可以是平鋪或粗體、斜體和下劃線的任意組合。運行庫實現工具可能沒有滿足您的標準的字體,它會將自由返回其確定的最相近的字體。
大多數的應用程序都能容許這些限制。不管怎樣,應用程序開發人員都希望將其溶入主機平臺的外觀和風格中。為使操作簡單,MIDP 2.0 提供了一個新的 getFont()
方法,提供一般地用于繪制靜態文本(如標題和標簽)的字體,以及用于繪制用戶在文本框中輸入的字符的字體。您可以確信這些字體存在,尺寸正確并與應用程序的其余部分一致。當調節給定字體的高度、寬度和基線,以及在運行時適當調整用戶界面時,應用程序應能夠在不同種類的設備上正確地運行。
然而一些應用程序需要對它們的外觀進行更多的控制。例如,大多數游戲需要一種與其圖形其他部分的風格相一致的字體。一些平臺可能存在特定的問題:當需要等寬字體時可能返回成比例字體,最小的字體可能太大了,并且一些字體可能不吸引人甚至無效。這些差異增大了創建和維護跨平臺軟件的難度。
MIDTerm 就是這種情況。我們需要確保其字體等寬的、足夠小以便在屏幕上能繪制最多的行和列并且清楚可讀,但在這一點上,繪制終端內容的 canvas 取決于設備實現。如果給定字體過大終端將不可用,并且如果字體不是等寬的,應用程序根本就不能工作。在我們的項目的這一點上,MIDP 模擬器返回的字體是等寬的且大小合適,但字體不是特別容易閱讀。
實現自定義字體將提供所需的字體控制,以便增強對 MIDterm 可用性的信心。同樣重要的是,因為不再依賴于每個平臺的本地字體實現方式,其跨不同的 MIDP 設備的性能將更具可預見性。
完成后,就會看到改進:
創建自定義字體
字體有兩個來源:
移動設備有限的處理能力使其無法使用 outline 字體。這些設備自然要使用 bitmap 字體,也正是我們將使用的。操作系統呈現我們的實現時不像內置字體那樣快,但為達到對字體外觀的絕對控制,這樣的時間犧牲能夠為人們所接受。
bitmap 字體的繪制指令是位到圖形環境中的位置的逐一映射。bitmap 字體中的每一個字符對應于從內存拷貝到屏幕的像素排列。換一種說法,每一個字符是通過拷貝到屏幕的一個后臺圖像來繪制的。從這個角度看,立即就能得到字體的定義(包含用于呈現字體的指令的文件)本身就可以是一個壓縮格式(如 PNG 或 JPEG)的圖像文件。我們可以把這個圖像讀到內存中,然后在需要繪制我們的字體的字符時,將它的各有關部分拷貝到屏幕。
那么第一步就是要創建一個這樣的圖像。在臺式計算機,使用您最喜歡的圖像編輯器來創建一個新的文檔。該文檔的高度應當就是字體所需的高度,并且足夠寬來容納要呈現的字符集中的每一個字符。那是多寬呢?
java 技術通常采用 Unicode Character Standard。Unicode 字符集有 95,000 多個字符,比我們能提供的要多,并遠遠超出我們的需要!因此 MIDTerm 的 Telnet 協議的實現方式是基于 7 位 ASCII 字符集的基礎上,該字符集只包含 128 個字符。因為 MIDTerm 需要等寬字體,文檔寬度應為一個字符寬度的 128 倍。例如,如果每一個字符高為 12 像素寬為 10 像素,為容納所有的 128 個字符,則需要高為 12 寬為 1,280 的圖像。
現在有了用于放置對應于 ASCII 代碼 0 到 127 的 128 個位置。為了能查明哪個字符放到哪個空位中,可能需要 ASCII 表 。請注意前 31 個字符是“控制字符”并且不能看到,可以保留為空白。
可以一個像素一個像素地地手動把每一個字符填充到槽中,但使用計算機中的某個字體的字符會更容易一些。確保這個字體是您自己的或有使用許可。我使用的是一種稱為 Anonymous 的字體,具有十分清晰的 6-point 顯示且免費使用。您可以從 Mark Simonson Studios 下載。
使用 Anonymous,我的字體圖像看起來是這樣的:
從定義來講,因為一個位只有 on 和 off 兩種狀態,因此一個 bitmap 字體僅需要使用兩種顏色。可以使用一種顏色來定義狀態為“開”的像素,用另一種不同的顏色來定義所有其他。由于 MIDP 2.0 設備需要支持透明的圖像,應將圖像編輯器的“關”的顏色標記為透明。透明的像素是不可見的,允許任何像素通過計算機中已有的應用程序 canvas 繪制。
您可能想把圖像文件存儲為 PNG 格式。一個單“位”的周圍區域顏色一致的圖像恰是這種圖像,這正是基本 PNG 壓縮算法所為之設計的。您的字體圖像文件將會壓縮得非常緊密:Anonymous 的 PNG 文件的大小只有 803 個字節大小。
自定義字體類
現在我們需要編寫一些軟件來讀取圖像文件并將其各部分呈現在屏幕上,我們將用到叫作 CustomFont
的一個新的類。
為了使 CustomFont
易于學習和使用,應盡可能地使其具有像標準 MIDP Font
類一樣的外觀和風格。由于為了使實現者可使用原生代碼進行優化,Font
已經被聲明為 final
,因此不能為其定義子類,但我們能夠模擬其公共界面。Font
的每個方法在 CustomFont
中也有,并且只有靜態工廠方法 getFont()
具有不同的簽名,它采用文件名字來替代慣用的字體類型:
public static CustomFont getFont( String inName, int inStyle, int inSize );
在許多 MIDP 應用程序中,通常通過調用 setFont()
方法來將一種字體傳遞到 Graphics
對象,然后調用該對象的 drawChars()
和 drawString()
方法來呈現文本。但這里我們不能采用同樣的模式。因為 CustomFont
不是 Font
的子類,我們不能將其傳遞到 Graphics.setFont() 。
相反,CustomFont
提供了公共的方法來繪制字符,這些方法模擬 Graphics
類的方法,每一種都用 Graphics
實例作為其第一個參數。
public void drawChar( Graphics g, char character, int x, int y, int anchor );public void drawChars( Graphics g, char[] data, int offset, int length, int x, int y, int anchor );public void drawString( Graphics g, String str, int x, int y, int anchor );public void drawSubstring( Graphics g, String str, int offset, int length, int x, int y, int anchor );
使用 CustomFont
的應用程序需要修改其繪制代碼來調用自定義字體類的這些方法,而不是 Graphics
對象的相應方法。
現在讓我們看一下字符位圖怎樣進入屏幕。可考慮兩種方法。
顯而易見的方法是把源圖像分解成為 128 個的圖像,一個圖像對應一個字符,如下所示:
...images = new Image[ 128 ]; for ( int i = 0; i < 128; i++ ){ images[i] = Image.createImage( image, i*width, 0, width, height, 0 );}...
從外部資源創建的圖像認為是不可變的。因為像素不能改變,運行庫能夠優化內存分配。使用 createImage()
方法從不可改變的圖像創建的圖像,也不能夠改變。因此,當有 128 個小圖像和一個大圖像時,可以期望運行庫足夠職能,能夠注意到二者都是不可修改的,并且在內存中只保存位圖的一份拷貝。即使這樣做,128 個圖像實例的純系統開銷也是一個小型設備難于應付的。
另一種方法是只用一個大圖像并像 IBM Selectric 打字機的連動球(typeball) 一樣使用。作為當時的革新,連動球(typeball)用球面上排列的提升起來的字符取代了用于每個字符的單個連動桿(typebar)。連動球根據每一次按鍵轉動且傾斜,使 Selectric 達到令人驚嘆的速度(每秒鐘幾乎15個字母)。連動球可能早已廢棄不用,但我們可以借用它的設計思想來使 MIDTerm 更具有高效率。為了繪制字符,點住圖像區域,左右移動圖像以便使所需的字符出現在剪輯區內,然后繪制圖像。每一個 Graphics
實例始終保持一個剪輯區,并且只有影響剪輯區的命令才真正執行。在這個例子中,可以借助運行庫來僅繪制適合于剪輯區的像素。方法如下:
public void drawChar( Graphics g, char character, int x, int y, int anchor ){ int clipX = g.getClipX(); int clipY = g.getClipY(); int clipW = g.getClipWidth(); int clipH = g.getClipHeight(); g.setClip( x, y, width, height ); g.drawImage( image, x - width*character, y, anchor ); g.setClip( clipX, clipY, clipW, clipH );}
這種方法的不利之處在于不得不在改變剪輯區前記住現存的剪輯區,以使能夠返回到 Graphics
創建時的狀態。每一次調用繪制方法之前都要存儲剪輯區,顯著的代價是:每次必須進行 4 次調用并分配 4 個 int。
我們陷入常見的時間-空間的權衡問題中:為節省內存,CustomFont
占用了更多的處理器時間,使用剪輯來取代對每一個字符分配單個圖像實例。在內存小的設備上,運行慢總不根本不能運行要強得多。
初始化時,有一項小任務 CustomFont
必須完成,這就是計算字體的基線。在字體渲染中,與字體的高度和寬度一樣,字體的基線也是另一個重要的決定因素,因為它定義了一個字體的字符位于一個文本行的哪個位置。如果一個文本行有不同的字體,需要將其基線對齊,以使其出現在同一個水平線上。
我們可以改變 CustomFont.getFont()
來要求調用者指定所選字體的基線,但有一種更好的方法。MIDP 2.0 使我們能夠動態檢測基線。getRGB()
方法使我們可直接訪問一個圖像的單個像素,并且一個簡單的探試就能確定哪行像素就是基線。
...// determine background color: assume it's at 0, 0image.getRGB( row, 0, 1, 0, 0, 1, 1 );background = row[0];// here's the heuristic: find the row on the bottom// half of the image with the most non-background pixelsfor ( int y = height/2; y < height; y++ ){ total = 0; image.getRGB( row, 0, imageWidth, 0, y, imageWidth, 1 ); for ( int x = 0; x < imageWidth; x++ ) { if ( row[x] != background ) total++; } if ( total > max ) { max = total; result = y; }}...
CustomFont
假定圖像中左上角像素為背景顏色。CustomFont
計算圖像的下半部分中的每一行的非背景像素,并且判斷具有最多前景像素的那一行作為基線。在運行時確定基線,使您在創建自定義字體位圖時無需為此事擔憂。
動態樣式設置
Font.getFont()
and CustomFont.getFont()
的簽名的第一個參數有所不同:標準的方法采用字體類型,而這里的方法采用的是圖像文件的名稱;但是二者都接受大小和樣式參數。CustomFont
忽略了所要求的字體大小,因為如果對每一個字體大小都在內存中提供一個獨立的圖像的話,代價太高,并且如果試圖在運行時縮放位圖,就可能造成最終結果難以辨識。但是,自定義字體類確實試圖尊重所要求的樣式,途徑是采用了一些簡單而高效的技術,這些技術改變了它在運行時繪制字符圖像的方式。
最簡單的是下劃線。就是在基線下兩個像素的位置,在字符下畫一條線。
...if ( ( style & Font.STYLE_UNDERLINED ) != 0 ){ g.drawLine( x, y + baseline + 2, x + width, y + baseline + 2 );}...
+加粗只是稍微復雜了一點。為了加粗字符,CustomFont
多繪制了一次字符,在右側加一列像素。因為背景是透明的,因此兩行像素重疊使字符加黑。
...if ( ( style & Font.STYLE_BOLD ) != 0 ){ // draw an additional time, one pixel to the right g.drawImage( image, x - width*character + 1, y, anchor );} ...
做斜體字時,CustomFont
使用剪輯來將字符的上半部分繪制得向右移動一個像素。效果出人意料地好。
...if ( ( style & Font.STYLE_ITALIC ) != 0 ){ g.setClip( x + 1, y, width, height/2 ); g.drawImage( image, x - width*character + 1, y, anchor ); g.setClip( x, y+height/2, width, height/2 ); g.drawImage( image, x - width*character, y, anchor );}...
下面是實際中的樣式:
STYLE_PLAIN
STYLE_UNDERLINED
STYLE_BOLD
STYLE_ITALIC
在繪制特定顏色的字符時會稍難一些。當 Graphics
用當前顏色渲染字體時,CustomFont
只能把像素拷貝到整個的字體圖像中,因此字符以原始圖像的采用的非透明的顏色出現。
MIDP 2.0 提供了一種從 getRGB()
返回的像素創建新圖像的方法。您可以復制一個字符的像素,掃描并修改每一個非背景像素的顏色,建立新的圖像,并將該圖像繪制屏幕上;但這種方法要占用大量的處理器資源而明顯地很慢。如果事先知道需要哪種顏色,在創建字體時可以在前景中使用該顏色。MIDTerm 采用是黑色背景,因此整個字體圖像含有白色字符,背景則是透明的。
使自定義的字體工作起來
因為 CustomFont
與 MIDP 的 Font
類非常接近于一致,MIDTerm 的 TelnetCanvas
類僅需要很少的改動就可以實現自定義字體的優勢。
font = Font.getFont( Font.FACE_MONOSPACE, Font.STYLE_SMALL, Font.SIZE_PLAIN );
font = CustomFont.getFont( "/mono.png", // the font bitmap file Font.SIZE_SMALL, // ignored Font.STYLE_PLAIN ); // no styling
g.setFont( font );g.drawChar( (char) b, insetX + x*fontWidth, insetY + y*fontHeight, g.TOP g.LEFT );
font.drawChar( g, (char) b, insetX + x*fontWidth, insetY + y*fontHeight, g.TOP g.LEFT );
進行這幾項改動后,應用程序會像以前一樣運行,而且更出色:更為清晰(如果運行稍慢一點),并且在多種的 MIDP 實現方面更具有可預見性。您同樣可以在其他應用程序中經過少量改動或不改動而使用 CustomFont。
我們采用了一種簡單而高效的技術來為 MIDP 應用程序提供了對文本繪制的絕對控制。您可以使用簡單的圖像編輯器來創建屬于自己的字體,并且,使用 MIDP 2.0 的圖像和圖像功能,可以以可接受的性能在屏幕上呈現您的字體。您可以設置自己所希望的字體樣式,甚至是在運行時是動態地設置。對于 MIDTerm ,自定義字體改進了可讀性和可用性,并減少了確保應用程序在多種平臺上順利運行所需的工作。
(出處:http://www.companysz.com)
新聞熱點
疑難解答