上一篇分享的程式碼在輸出一般的英數字是沒有問題,但是當輸入的字是中文字時,反解的結果總是變成「?」,讓我覺得十分奇怪,明明在維基百科上看到 QR Code 的說明是支援 UTF-8 的文字,而 Flash 本身也支援多國語系,難不成是因為 Flash 實際上走的是 UTF-16 ,造成文字因為編碼不同造成無法反解?
在沒有什麼文件的狀態下,我開始程式碼的 hack 旅程…
首先當然是由實際運作編碼的 QRCodeWriter 開始尋找,在 encode 方法下,看到了使用 com.google.zxing.qrcode.encoder.Encoder 再次編碼方法。
Encoder.encode(contents, errorCorrectionLevel, code);再去看看 Encoder 的程式嗎,依然之傳入作為編碼對象的「content」變數作依據,在 encode 方法的程式碼看到一些東西。
public static function encode(content:String , ecLevel:ErrorCorrectionLevel, qrCode:QRCode,hints:HashTable=null ):void
{
var encoding:String = hints == null ? null : (hints._get(EncodeHintType.CHARACTER_SET) as String);
// 後面會提及,這裡的 encoding 就是表示輸入文字所用的編碼
if (encoding == null)
{
encoding = DEFAULT_BYTE_MODE_ENCODING;
// 如果沒有指定編碼,就設定為預設值
}
// Step 1: Choose the mode (encoding).
var mode:Mode = chooseMode(content, encoding);
// 這裡沒什麼好說的,就是 QR Code Mode
// Step 2: Append "bytes" into "dataBits" in appropriate encoding.
var dataBits:BitVector = new BitVector();
appendBytes(content, mode, dataBits, encoding);
// 這裡是重點,看起來文字是由這裡編碼後再轉換成 QR Code 資訊
// 後略…
}
再繼續查,查到 appendBytes 這個方法…
/**
* Append "bytes" in "mode" mode (encoding) into "bits". On success, store the result in "bits".
*/
public static function appendBytes(content:String , mode:Mode , bits:BitVector , encoding:String ):void
{
if (mode == Mode.NUMERIC) {
appendNumericBytes(content, bits);
} else if (mode == Mode.ALPHANUMERIC) {
appendAlphanumericBytes(content, bits);
} else if (mode == Mode.BYTE) {
append8BitBytes(content, bits, encoding);
// 以中斷點進行測試,表示 QR Code 的編碼會落在這個部分…
} else if (mode == Mode.KANJI) {
appendKanjiBytes(content, bits);
} else {
throw new WriterException("Invalid mode: " + mode);
}
}
最後找到 appendBitBytes 方法…
public static function append8BitBytes(content:String , bits:BitVector , encoding:String ):void
{
var bytes:ByteArray = new ByteArray();
try {
//bytes = content.getBytes(encoding);
if ((encoding == "Shift_JIS") || (encoding == "SJIS")) { bytes.writeMultiByte(content, "shift-jis");}
else if (encoding == "Cp437") { bytes.writeMultiByte(content, "IBM437"); }
else if (encoding == "ISO8859_2") { bytes.writeMultiByte(content, "iso-8859-2"); }
else if (encoding == "ISO8859_3") { bytes.writeMultiByte(content, "iso-8859-3"); }
else if (encoding == "ISO8859_4") { bytes.writeMultiByte(content, "iso-8859-4"); }
else if (encoding == "ISO8859_5") { bytes.writeMultiByte(content, "iso-8859-5"); }
else if (encoding == "ISO8859_6") { bytes.writeMultiByte(content, "iso-8859-6"); }
else if (encoding == "ISO8859_7") { bytes.writeMultiByte(content, "iso-8859-7"); }
else if (encoding == "ISO8859_8") { bytes.writeMultiByte(content, "iso-8859-8"); }
else if (encoding == "ISO8859_9") { bytes.writeMultiByte(content, "iso-8859-9"); }
else if (encoding == "ISO8859_11"){ bytes.writeMultiByte(content, "iso-8859-11"); }
else if (encoding == "ISO8859_15"){ bytes.writeMultiByte(content, "iso-8859-15"); }
else if ((encoding == "ISO-8859-1") || (encoding == "ISO8859-1")) { bytes.writeMultiByte(content, "iso-8859-1"); }
else if ((encoding == "UTF-8") || (encoding == "UTF8")) { bytes.writeMultiByte(content, "utf-8"); }
// 真多編碼,重點在這裡有「UTF-8」的存在,所以要確保文字傳入是以這個模式轉換
// 看到了嗎?就是用 encoding 這個參數來表示編碼
else
{
//other encodings not supported
throw new Error("Encoding "+ encoding + " not supported");
}
bytes.position = 0;
} catch (uee:Error) {
throw new WriterException(uee.toString());
}
for (var i:int = 0; i < bytes.length; ++i) {
bits.appendBits(bytes[i], 8);
}
}
但是以中斷點查詢,並不是「UTF-8」而是「ISO-8859-1」這個編碼,它並不代表任何一種編碼,而是表示「依所在作業系統的預設編碼」,而 Windows 7 的預設編碼為?雖然號稱支援多國語系,但是仍然很神奇地使用傳統的 Big5 中文編碼,也難怪中文字編碼後解析不出來。
既然找到問題,再接下來就開始解決它。由前文中提到,encoding 表編碼,而編碼如何被傳入?是由下面這段而來…
var encoding:String = hints == null ? null : (hints._get(EncodeHintType.CHARACTER_SET) as String);由 hints 這個參數傳入資訊,它是一個由作者自得開發,一個類似 Java 的 Map 類別,叫作 HashTable,使用字串常入 EncodeHintType.CHARACTER_SET 作 Key。
因此我修改了我的程式,將…
var writer:Writer = new QRCodeWriter(); var mb:BitMatrix = writer.encode(content.text, BarcodeFormat.QR_CODE, 0, 0) as BitMatrix;改成…
var writer:Writer = new QRCodeWriter(); var codeinfo:HashTable = new HashTable(); codeinfo._put(EncodeHintType.CHARACTER_SET, "UTF-8"); var mb:BitMatrix = writer.encode(content.text, BarcodeFormat.QR_CODE, 0, 0, codeinfo) as BitMatrix;
滿心期望的執行測試,結果什麼都沒有發生。再次利用中斷點查詢程式中變數內容的變化,後來發現在 QRCodeWriter 的程式中…
Encoder.encode(contents, errorCorrectionLevel, code);並沒有將我傳入的 hints 再 pass 給後面的 Encoder 類別,所以又變成以預設值來進行編碼。
所以我修改它,將它改成…
Encoder.encode(contents, errorCorrectionLevel, code, hints); // hints 是本來函數呼叫就有的戔數,不是我多加的
自此,這個程式也可以將中文字編碼,順利被手機掃描出結果。
後記︰
QR Code 雖然仍然有資訊承截量的上限,不過已經讓我吃了驚,可以將新聞網站上兩段文字貼入,在幾秒內就讀出並反解。雖然到第三段就無法解析,還不知道是因為字數過多,還是因為遇到第三個「換行字元」,但是由這中間也看到不少可能應用。
最後分享我測試使用的程式碼。(官方的函數請自行到 google code 下載)
<?xml version="1.0" encoding="utf-8"?>
<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx">
<fx:Declarations>
<!-- Place non-visual elements (e.g., services, value objects) here -->
</fx:Declarations>
<fx:Script>
<![CDATA[
import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.Writer;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.common.flexdatatypes.HashTable;
import com.google.zxing.qrcode.QRCodeWriter;
protected override function initializationComplete():void {
super.initializationComplete();
}
private function createQRCode():void {
var writer:Writer = new QRCodeWriter();
var codeinfo:HashTable = new HashTable();
codeinfo._put(EncodeHintType.CHARACTER_SET, "UTF-8");
var mb:BitMatrix = writer.encode(content.text, BarcodeFormat.QR_CODE, 0, 0, codeinfo) as BitMatrix;
var width:int = mb.width;
var height:int = mb.height;
var color:uint;
var cw:Number = canvas.width;
var ch:Number = canvas.height;
var sx:Number;
var sy:Number;
if(cw > ch) {
sx = (cw - ch)/2;
sy = 0;
cw = ch;
} else {
sy = (ch - cw)/2;
sx = 0;
ch = cw;
}
canvas.graphics.clear();
for(var y:int=0; y<height; y++) {
for(var x:int=0; x<width; x++) {
color = mb._get(x, y)?0:0xFFFFFF;
canvas.graphics.beginFill(color, 1);
canvas.graphics.drawRect(
sx+cw/width*x, sy+ch/height*y,
cw/width, ch/height
);
canvas.graphics.endFill();
}
}
}
]]>
</fx:Script>
<s:layout>
<s:VerticalLayout horizontalAlign="center" />
</s:layout>
<s:Group id="canvas" width="100%" height="100%">
</s:Group>
<s:HGroup>
<s:TextInput id="content" width="100%" />
<s:Button label="Create QRCode" click="createQRCode()" />
</s:HGroup>
</s:WindowedApplication>
沒有留言:
張貼留言