如何用iTextSharp生成的QR条码对中文文本进行编码?

时间:2012-03-14 11:11:21

标签: itextsharp qr-code

我正在尝试使用iTextSharp在PDF文件中绘制QR条形码。如果我使用英文文本条形码很好,它们会被正确解码,但如果我使用中文文本,条形码会被解码为问号。例如,该字符'测'(\ u6D4B)被解码为'?'。我尝试了所有支持的字符集,但没有一个帮助。
为了正确编码中文文本,我应该将哪些参数组合用于iTextSharp中的QR条码?

3 个答案:

答案 0 :(得分:5)

iText和iTextSharp显然本身不支持此功能,但您可以编写一些代码来自行处理。诀窍是让QR代码解析器只使用任意字节数组而不是字符串。真正好的是iTextSharp代码几乎已经准备就绪,但没有公开这些功能。不幸的是,许多必需的类都是sealed所以你不能只是将它们子类化,你必须重新创建它们。您可以下载整个源并添加这些更改,也可以只创建具有相同名称的单独类。 (请检查许可证以确保您可以这样做。)我在下面的更改没有任何错误更正,所以请确保您也这样做。

您需要重新创建的第一个类是iTextSharp.text.pdf.qrcode.BlockPair,您需要做的唯一更改是使构造函数public而不是internal。 (如果您要创建自己的代码而不修改现有代码,则只需执行此操作。)

第二节课是iTextSharp.text.pdf.qrcode.Encoder。这是我们将做出最大改变的地方。向Append8BitBytes添加如下所示的重载:

static void Append8BitBytes(byte[] bytes, BitVector bits) {
    for (int i = 0; i < bytes.Length; ++i) {
        bits.AppendBits(bytes[i], 8);
    }
}

此方法的字符串版本将文本转换为字节数组,然后使用上面的内容,因此我们只是删除了中间人。接下来,向构造函数添加一个新的重载,它接受一个字节数组而不是字符串。然后我们将切断字符串检测部分并强制系统进入字节模式,否则下面的代码几乎相同。

    public static void Encode(byte[] bytes, ErrorCorrectionLevel ecLevel, IDictionary<EncodeHintType, Object> hints, QRCode qrCode) {
        String encoding = DEFAULT_BYTE_MODE_ENCODING;

        // Step 1: Choose the mode (encoding).
        Mode mode = Mode.BYTE;

        // Step 2: Append "bytes" into "dataBits" in appropriate encoding.
        BitVector dataBits = new BitVector();
        Append8BitBytes(bytes, dataBits);

        // Step 3: Initialize QR code that can contain "dataBits".
        int numInputBytes = dataBits.SizeInBytes();
        InitQRCode(numInputBytes, ecLevel, mode, qrCode);

        // Step 4: Build another bit vector that contains header and data.
        BitVector headerAndDataBits = new BitVector();

        // Step 4.5: Append ECI message if applicable
        if (mode == Mode.BYTE && !DEFAULT_BYTE_MODE_ENCODING.Equals(encoding)) {
            CharacterSetECI eci = CharacterSetECI.GetCharacterSetECIByName(encoding);
            if (eci != null) {
                AppendECI(eci, headerAndDataBits);
            }
        }

        AppendModeInfo(mode, headerAndDataBits);

        int numLetters = dataBits.SizeInBytes();
        AppendLengthInfo(numLetters, qrCode.GetVersion(), mode, headerAndDataBits);
        headerAndDataBits.AppendBitVector(dataBits);

        // Step 5: Terminate the bits properly.
        TerminateBits(qrCode.GetNumDataBytes(), headerAndDataBits);

        // Step 6: Interleave data bits with error correction code.
        BitVector finalBits = new BitVector();
        InterleaveWithECBytes(headerAndDataBits, qrCode.GetNumTotalBytes(), qrCode.GetNumDataBytes(),
            qrCode.GetNumRSBlocks(), finalBits);

        // Step 7: Choose the mask pattern and set to "qrCode".
        ByteMatrix matrix = new ByteMatrix(qrCode.GetMatrixWidth(), qrCode.GetMatrixWidth());
        qrCode.SetMaskPattern(ChooseMaskPattern(finalBits, qrCode.GetECLevel(), qrCode.GetVersion(),
            matrix));

        // Step 8.  Build the matrix and set it to "qrCode".
        MatrixUtil.BuildMatrix(finalBits, qrCode.GetECLevel(), qrCode.GetVersion(),
            qrCode.GetMaskPattern(), matrix);
        qrCode.SetMatrix(matrix);
        // Step 9.  Make sure we have a valid QR Code.
        if (!qrCode.IsValid()) {
            throw new WriterException("Invalid QR code: " + qrCode.ToString());
        }
    }

第三个类是iTextSharp.text.pdf.qrcode.QRCodeWriter,我们只需要添加一个重载的Encode方法支持一个字节数组,并且调用是上面创建的新构造函数:

    public ByteMatrix Encode(byte[] bytes, int width, int height, IDictionary<EncodeHintType, Object> hints) {
        ErrorCorrectionLevel errorCorrectionLevel = ErrorCorrectionLevel.L;
        if (hints != null && hints.ContainsKey(EncodeHintType.ERROR_CORRECTION))
            errorCorrectionLevel = (ErrorCorrectionLevel)hints[EncodeHintType.ERROR_CORRECTION];

        QRCode code = new QRCode();
        Encoder.Encode(bytes, errorCorrectionLevel, hints, code);
        return RenderResult(code, width, height);
    }

最后一个类是iTextSharp.text.pdf.BarcodeQRCode,我们再一次添加了新的构造函数重载:

    public BarcodeQRCode(byte[] bytes, int width, int height, IDictionary<EncodeHintType, Object> hints) {
        newCode.QRCodeWriter qc = new newCode.QRCodeWriter();
        bm = qc.Encode(bytes, width, height, hints);
    }

最后一个技巧是确保在调用时包含字节顺序标记(BOM),以便解码器知道正确解码,在本例中为UTF-8。

//Create an encoder that supports outputting a BOM
System.Text.Encoding enc = new System.Text.UTF8Encoding(true, true);

//Get the BOM
byte[] bom = enc.GetPreamble();

//Get the raw bytes for the string
byte[] bytes = enc.GetBytes("测");

//Combine the byte arrays
byte[] final = new byte[bom.Length + bytes.Length];
System.Buffer.BlockCopy(bom, 0, final, 0, bom.Length);
System.Buffer.BlockCopy(bytes, 0, final, bom.Length, bytes.Length);

//Create are barcode using our new constructor
var q = new BarcodeQRCode(final, 100, 100, null);

//Add it to the document
doc.Add(q.GetImage());

答案 1 :(得分:1)

看起来你可能运气不好。我也尝试了,并得到了与你相同的结果。然后看了Java API

  

“* CHARACTER_SET值是字符串,可以是Cp437,Shift_JIS和   ISO-8859-1至ISO-8859-16。默认值为ISO-8859-1。*“

最后,查看了iTextSharp BarcodeQRCode类源代码,以确认只支持那些字符集。我绝不是Unicode或编码的权威,但根据ISO/IEC 8859,上面的字符集对中文不起作用。

答案 2 :(得分:1)

基本上与Chris在答案中所做的相同的技巧可以通过在条形码提示中指定UTF-8字符集来实现。

var hints = new Dictionary<EncodeHintType, Object>() {{EncodeHintType.CHARACTER_SET, "UTF-8"}};
var q = new BarcodeQRCode("\u6D4B", 100, 100, hints);

如果您想更安全,可以像Chris建议的那样使用BOM字符'\uFEFF'开始字符串,因此它将是"\uFEFF\u6D4B"

遗憾的是,UTF-8不受QR码规范的支持,关于这个主题有很多讨论,但事实是大多数QR码阅读器都能正确读取这种方法创建的代码。