Added rendering fix from beyonddeath
[zxing.git] / csharp / qrcode / encoder / Encoder.cs
1 /*\r
2 * Copyright 2007 ZXing authors\r
3 *\r
4 * Licensed under the Apache License, Version 2.0 (the "License");\r
5 * you may not use this file except in compliance with the License.\r
6 * You may obtain a copy of the License at\r
7 *\r
8 *      http://www.apache.org/licenses/LICENSE-2.0\r
9 *\r
10 * Unless required by applicable law or agreed to in writing, software\r
11 * distributed under the License is distributed on an "AS IS" BASIS,\r
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
13 * See the License for the specific language governing permissions and\r
14 * limitations under the License.\r
15 */\r
16 using System;\r
17 using System.Text;\r
18 using System.Collections;\r
19 using com.google.zxing;\r
20 using com.google.zxing.common;\r
21 using com.google.zxing.common.reedsolomon;\r
22 using com.google.zxing.qrcode.decoder;\r
23 using com.google.zxing.qrcode;\r
24 \r
25 namespace com.google.zxing.qrcode.encoder\r
26 {\r
27     using Version=com.google.zxing.qrcode.decoder.Version;   \r
28     public sealed class Encoder\r
29     { \r
30          // The original table is defined in the table 5 of JISX0510:2004 (p.19).\r
31           private static int[] ALPHANUMERIC_TABLE = {\r
32               -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,  // 0x00-0x0f\r
33               -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,  // 0x10-0x1f\r
34               36, -1, -1, -1, 37, 38, -1, -1, -1, -1, 39, 40, -1, 41, 42, 43,  // 0x20-0x2f\r
35               0,   1,  2,  3,  4,  5,  6,  7,  8,  9, 44, -1, -1, -1, -1, -1,  // 0x30-0x3f\r
36               -1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,  // 0x40-0x4f\r
37               25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1,  // 0x50-0x5f\r
38           };\r
39 \r
40           private Encoder() {\r
41           }\r
42 \r
43           // The mask penalty calculation is complicated.  See Table 21 of JISX0510:2004 (p.45) for details.\r
44           // Basically it applies four rules and summate all penalties.\r
45           private static int calculateMaskPenalty(ByteMatrix matrix) {\r
46             int penalty = 0;\r
47             penalty += MaskUtil.applyMaskPenaltyRule1(matrix);\r
48             penalty += MaskUtil.applyMaskPenaltyRule2(matrix);\r
49             penalty += MaskUtil.applyMaskPenaltyRule3(matrix);\r
50             penalty += MaskUtil.applyMaskPenaltyRule4(matrix);\r
51             return penalty;\r
52           }\r
53 \r
54           private class BlockPair {\r
55 \r
56             private ByteArray dataBytes;\r
57             private ByteArray errorCorrectionBytes;\r
58 \r
59             public BlockPair(ByteArray data, ByteArray errorCorrection) {\r
60               dataBytes = data;\r
61               errorCorrectionBytes = errorCorrection;\r
62             }\r
63 \r
64             public ByteArray getDataBytes() {\r
65               return dataBytes;\r
66             }\r
67 \r
68             public ByteArray getErrorCorrectionBytes() {\r
69               return errorCorrectionBytes;\r
70             }\r
71 \r
72           }\r
73 \r
74           // Encode "bytes" with the error correction level "getECLevel". The encoding mode will be chosen\r
75           // internally by chooseMode(). On success, store the result in "qrCode" and return true.\r
76           // We recommend you to use QRCode.EC_LEVEL_L (the lowest level) for\r
77           // "getECLevel" since our primary use is to show QR code on desktop screens. We don't need very\r
78           // strong error correction for this purpose.\r
79           //\r
80           // Note that there is no way to encode bytes in MODE_KANJI. We might want to add EncodeWithMode()\r
81           // with which clients can specify the encoding mode. For now, we don't need the functionality.\r
82           public static void encode(String content, ErrorCorrectionLevel ecLevel, QRCode qrCode)\r
83           {\r
84             // Step 1: Choose the mode (encoding).\r
85             Mode mode = chooseMode(content);\r
86 \r
87             // Step 2: Append "bytes" into "dataBits" in appropriate encoding.\r
88             BitVector dataBits = new BitVector();\r
89             appendBytes(content, mode, dataBits);\r
90             // Step 3: Initialize QR code that can contain "dataBits".\r
91             int numInputBytes = dataBits.sizeInBytes();\r
92             initQRCode(numInputBytes, ecLevel, mode, qrCode);\r
93 \r
94             // Step 4: Build another bit vector that contains header and data.\r
95             BitVector headerAndDataBits = new BitVector();\r
96             appendModeInfo(qrCode.getMode(), headerAndDataBits);\r
97             appendLengthInfo(content.Length, qrCode.getVersion(), qrCode.getMode(), headerAndDataBits);\r
98             headerAndDataBits.appendBitVector(dataBits);\r
99 \r
100             // Step 5: Terminate the bits properly.\r
101             terminateBits(qrCode.getNumDataBytes(), headerAndDataBits);\r
102 \r
103             // Step 6: Interleave data bits with error correction code.\r
104             BitVector finalBits = new BitVector();\r
105             interleaveWithECBytes(headerAndDataBits, qrCode.getNumTotalBytes(), qrCode.getNumDataBytes(),\r
106                 qrCode.getNumRSBlocks(), finalBits);\r
107 \r
108             // Step 7: Choose the mask pattern and set to "qrCode".\r
109             ByteMatrix matrix = new ByteMatrix(qrCode.getMatrixWidth(), qrCode.getMatrixWidth());\r
110             qrCode.setMaskPattern(chooseMaskPattern(finalBits, qrCode.getECLevel(), qrCode.getVersion(),\r
111                 matrix));\r
112 \r
113             // Step 8.  Build the matrix and set it to "qrCode".\r
114             MatrixUtil.buildMatrix(finalBits, qrCode.getECLevel(), qrCode.getVersion(),\r
115                 qrCode.getMaskPattern(), matrix);\r
116             qrCode.setMatrix(matrix);\r
117             // Step 9.  Make sure we have a valid QR Code.\r
118             if (!qrCode.isValid()) {\r
119               throw new WriterException("Invalid QR code: " + qrCode.toString());\r
120             }\r
121           }\r
122 \r
123           // Return the code point of the table used in alphanumeric mode. Return -1 if there is no\r
124           // corresponding code in the table.\r
125           static int getAlphanumericCode(int code) {\r
126             if (code < ALPHANUMERIC_TABLE.Length) {\r
127               return ALPHANUMERIC_TABLE[code];\r
128             }\r
129             return -1;\r
130           }\r
131 \r
132           // Choose the best mode by examining the content.\r
133           //\r
134           // Note that this function does not return MODE_KANJI, as we cannot distinguish Shift_JIS from\r
135           // other encodings such as ISO-8859-1, from data bytes alone. For example "\xE0\xE0" can be\r
136           // interpreted as one character in Shift_JIS, but also two characters in ISO-8859-1.\r
137           //\r
138           // JAVAPORT: This MODE_KANJI limitation sounds like a problem for us.\r
139           public static Mode chooseMode(String content) {\r
140             bool hasNumeric = false;\r
141             bool hasAlphanumeric = false;\r
142             for (int i = 0; i < content.Length; ++i) {\r
143               char c = content[i];\r
144               if (c >= '0' && c <= '9') {\r
145                 hasNumeric = true;\r
146               } else if (getAlphanumericCode(c) != -1) {\r
147                 hasAlphanumeric = true;\r
148               } else {\r
149                 return Mode.BYTE;\r
150               }\r
151             }\r
152             if (hasAlphanumeric) {\r
153               return Mode.ALPHANUMERIC;\r
154             } else if (hasNumeric) {\r
155               return Mode.NUMERIC;\r
156             }\r
157             return Mode.BYTE;\r
158           }\r
159 \r
160           private static int chooseMaskPattern(BitVector bits, ErrorCorrectionLevel ecLevel, int version,ByteMatrix matrix){\r
161               try{\r
162                   int minPenalty = int.MaxValue;  // Lower penalty is better.\r
163                   int bestMaskPattern = -1;\r
164                   // We try all mask patterns to choose the best one.\r
165                   for (int maskPattern = 0; maskPattern < QRCode.NUM_MASK_PATTERNS; maskPattern++)\r
166                   {\r
167                       MatrixUtil.buildMatrix(bits, ecLevel, version, maskPattern, matrix);\r
168                       int penalty = calculateMaskPenalty(matrix);\r
169                       if (penalty < minPenalty)\r
170                       {\r
171                           minPenalty = penalty;\r
172                           bestMaskPattern = maskPattern;\r
173                       }\r
174                   }\r
175                   return bestMaskPattern;              \r
176               }catch(Exception e){\r
177                   throw new ReaderException(e.Message);\r
178               }            \r
179           }\r
180 \r
181           // Initialize "qrCode" according to "numInputBytes", "ecLevel", and "mode". On success, modify\r
182           // "qrCode".\r
183           private static void initQRCode(int numInputBytes, ErrorCorrectionLevel ecLevel, Mode mode, QRCode qrCode)\r
184           {\r
185               try\r
186               {\r
187                 qrCode.setECLevel(ecLevel);\r
188                 qrCode.setMode(mode);\r
189 \r
190                 // In the following comments, we use numbers of Version 7-H.\r
191                 for (int versionNum = 1; versionNum <= 40; versionNum++) {\r
192                   Version version = Version.getVersionForNumber(versionNum);\r
193                   // numBytes = 196\r
194                   int numBytes = version.getTotalCodewords();\r
195                   // getNumECBytes = 130\r
196                   Version.ECBlocks ecBlocks = version.getECBlocksForLevel(ecLevel);\r
197                   int numEcBytes = ecBlocks.getTotalECCodewords();\r
198                   // getNumRSBlocks = 5\r
199                   int numRSBlocks = ecBlocks.getNumBlocks();\r
200                   // getNumDataBytes = 196 - 130 = 66\r
201                   int numDataBytes = numBytes - numEcBytes;\r
202                   // We want to choose the smallest version which can contain data of "numInputBytes" + some\r
203                   // extra bits for the header (mode info and length info). The header can be three bytes\r
204                   // (precisely 4 + 16 bits) at most. Hence we do +3 here.\r
205                   if (numDataBytes >= numInputBytes + 3) {\r
206                     // Yay, we found the proper rs block info!\r
207                     qrCode.setVersion(versionNum);\r
208                     qrCode.setNumTotalBytes(numBytes);\r
209                     qrCode.setNumDataBytes(numDataBytes);\r
210                     qrCode.setNumRSBlocks(numRSBlocks);\r
211                     // getNumECBytes = 196 - 66 = 130\r
212                     qrCode.setNumECBytes(numEcBytes);\r
213                     // matrix width = 21 + 6 * 4 = 45\r
214                     qrCode.setMatrixWidth(version.getDimensionForVersion());\r
215                     return;\r
216                   }\r
217                 }\r
218                 throw new WriterException("Cannot find proper rs block info (input data too big?)");\r
219               }\r
220               catch(Exception e){\r
221                 throw new WriterException(e.Message);\r
222               }\r
223           }\r
224 \r
225           // Terminate bits as described in 8.4.8 and 8.4.9 of JISX0510:2004 (p.24).\r
226           static void terminateBits(int numDataBytes, BitVector bits){\r
227             int capacity = numDataBytes << 3;\r
228             if (bits.size() > capacity) {\r
229               throw new WriterException("data bits cannot fit in the QR Code" + bits.size() + " > " + capacity);\r
230             }\r
231             // Append termination bits. See 8.4.8 of JISX0510:2004 (p.24) for details.\r
232             for (int i = 0; i < 4 && bits.size() < capacity; ++i) {\r
233               bits.appendBit(0);\r
234             }\r
235             int numBitsInLastByte = bits.size() % 8;\r
236             // If the last byte isn't 8-bit aligned, we'll add padding bits.\r
237             if (numBitsInLastByte > 0) {\r
238               int numPaddingBits = 8 - numBitsInLastByte;\r
239               for (int i = 0; i < numPaddingBits; ++i) {\r
240                 bits.appendBit(0);\r
241               }\r
242             }\r
243             // Should be 8-bit aligned here.\r
244             if (bits.size() % 8 != 0) {\r
245               throw new WriterException("Number of bits is not a multiple of 8");\r
246             }\r
247             // If we have more space, we'll fill the space with padding patterns defined in 8.4.9 (p.24).\r
248             int numPaddingBytes = numDataBytes - bits.sizeInBytes();\r
249             for (int i = 0; i < numPaddingBytes; ++i) {\r
250               if (i % 2 == 0) {\r
251                 bits.appendBits(0xec, 8);\r
252               } else {\r
253                 bits.appendBits(0x11, 8);\r
254               }\r
255             }\r
256             if (bits.size() != capacity) {\r
257               throw new WriterException("Bits size does not equal capacity");\r
258             }\r
259           }\r
260 \r
261           // Get number of data bytes and number of error correction bytes for block id "blockID". Store\r
262           // the result in "numDataBytesInBlock", and "numECBytesInBlock". See table 12 in 8.5.1 of\r
263           // JISX0510:2004 (p.30)\r
264           static void getNumDataBytesAndNumECBytesForBlockID(int numTotalBytes, int numDataBytes,\r
265               int numRSBlocks, int blockID, int[] numDataBytesInBlock,int[] numECBytesInBlock)  {\r
266             if (blockID >= numRSBlocks) {\r
267               throw new WriterException("Block ID too large");\r
268             }\r
269             // numRsBlocksInGroup2 = 196 % 5 = 1\r
270             int numRsBlocksInGroup2 = numTotalBytes % numRSBlocks;\r
271             // numRsBlocksInGroup1 = 5 - 1 = 4\r
272             int numRsBlocksInGroup1 = numRSBlocks - numRsBlocksInGroup2;\r
273             // numTotalBytesInGroup1 = 196 / 5 = 39\r
274             int numTotalBytesInGroup1 = numTotalBytes / numRSBlocks;\r
275             // numTotalBytesInGroup2 = 39 + 1 = 40\r
276             int numTotalBytesInGroup2 = numTotalBytesInGroup1 + 1;\r
277             // numDataBytesInGroup1 = 66 / 5 = 13\r
278             int numDataBytesInGroup1 = numDataBytes / numRSBlocks;\r
279             // numDataBytesInGroup2 = 13 + 1 = 14\r
280             int numDataBytesInGroup2 = numDataBytesInGroup1 + 1;\r
281             // numEcBytesInGroup1 = 39 - 13 = 26\r
282             int numEcBytesInGroup1 = numTotalBytesInGroup1 - numDataBytesInGroup1;\r
283             // numEcBytesInGroup2 = 40 - 14 = 26\r
284             int numEcBytesInGroup2 = numTotalBytesInGroup2 - numDataBytesInGroup2;\r
285             // Sanity checks.\r
286             // 26 = 26\r
287             if (numEcBytesInGroup1 != numEcBytesInGroup2) {\r
288               throw new WriterException("EC bytes mismatch");\r
289             }\r
290             // 5 = 4 + 1.\r
291             if (numRSBlocks != numRsBlocksInGroup1 + numRsBlocksInGroup2) {\r
292               throw new WriterException("RS blocks mismatch");\r
293             }\r
294             // 196 = (13 + 26) * 4 + (14 + 26) * 1\r
295             if (numTotalBytes !=\r
296                 ((numDataBytesInGroup1 + numEcBytesInGroup1) *\r
297                     numRsBlocksInGroup1) +\r
298                     ((numDataBytesInGroup2 + numEcBytesInGroup2) *\r
299                         numRsBlocksInGroup2)) {\r
300               throw new WriterException("Total bytes mismatch");\r
301             }\r
302 \r
303             if (blockID < numRsBlocksInGroup1) {\r
304               numDataBytesInBlock[0] = numDataBytesInGroup1;\r
305               numECBytesInBlock[0] = numEcBytesInGroup1;\r
306             } else {\r
307               numDataBytesInBlock[0] = numDataBytesInGroup2;\r
308               numECBytesInBlock[0] = numEcBytesInGroup2;\r
309             }\r
310           }\r
311 \r
312           // Interleave "bits" with corresponding error correction bytes. On success, store the result in\r
313           // "result" and return true. The interleave rule is complicated. See 8.6\r
314           // of JISX0510:2004 (p.37) for details.\r
315           static void interleaveWithECBytes(BitVector bits, int numTotalBytes,\r
316               int numDataBytes, int numRSBlocks, BitVector result) {\r
317 \r
318             // "bits" must have "getNumDataBytes" bytes of data.\r
319             if (bits.sizeInBytes() != numDataBytes) {\r
320               throw new WriterException("Number of bits and data bytes does not match");\r
321             }\r
322 \r
323             // Step 1.  Divide data bytes into blocks and generate error correction bytes for them. We'll\r
324             // store the divided data bytes blocks and error correction bytes blocks into "blocks".\r
325             int dataBytesOffset = 0;\r
326             int maxNumDataBytes = 0;\r
327             int maxNumEcBytes = 0;\r
328 \r
329             // Since, we know the number of reedsolmon blocks, we can initialize the vector with the number.\r
330             ArrayList blocks = new ArrayList(numRSBlocks);\r
331 \r
332             for (int i = 0; i < numRSBlocks; ++i) {\r
333               int[] numDataBytesInBlock = new int[1];\r
334               int[] numEcBytesInBlock = new int[1];\r
335               getNumDataBytesAndNumECBytesForBlockID(\r
336                   numTotalBytes, numDataBytes, numRSBlocks, i,\r
337                   numDataBytesInBlock, numEcBytesInBlock);\r
338 \r
339               ByteArray dataBytes = new ByteArray();\r
340               dataBytes.set(bits.getArray(), dataBytesOffset, numDataBytesInBlock[0]);\r
341               ByteArray ecBytes = generateECBytes(dataBytes, numEcBytesInBlock[0]);\r
342               blocks.Add(new BlockPair(dataBytes, ecBytes));\r
343 \r
344               maxNumDataBytes = Math.Max(maxNumDataBytes, dataBytes.size());\r
345               maxNumEcBytes = Math.Max(maxNumEcBytes, ecBytes.size());\r
346               dataBytesOffset += numDataBytesInBlock[0];\r
347             }\r
348             if (numDataBytes != dataBytesOffset) {\r
349               throw new WriterException("Data bytes does not match offset");\r
350             }\r
351 \r
352             // First, place data blocks.\r
353             for (int i = 0; i < maxNumDataBytes; ++i) {\r
354               for (int j = 0; j < blocks.Count; ++j) {\r
355                 ByteArray dataBytes = ((BlockPair) blocks[j]).getDataBytes();\r
356                 if (i < dataBytes.size()) {\r
357                   result.appendBits(dataBytes.at(i), 8);\r
358                 }\r
359               }\r
360             }\r
361             // Then, place error correction blocks.\r
362             for (int i = 0; i < maxNumEcBytes; ++i) {\r
363               for (int j = 0; j < blocks.Count; ++j) {\r
364                 ByteArray ecBytes = ((BlockPair) blocks[j]).getErrorCorrectionBytes();\r
365                 if (i < ecBytes.size()) {\r
366                   result.appendBits(ecBytes.at(i), 8);\r
367                 }\r
368               }\r
369             }\r
370             if (numTotalBytes != result.sizeInBytes()) {  // Should be same.\r
371               throw new WriterException("Interleaving error: " + numTotalBytes + " and " + result.sizeInBytes() +\r
372                 " differ.");\r
373             }\r
374           }\r
375 \r
376           static ByteArray generateECBytes(ByteArray dataBytes, int numEcBytesInBlock) {\r
377             int numDataBytes = dataBytes.size();\r
378             int[] toEncode = new int[numDataBytes + numEcBytesInBlock];\r
379             for (int i = 0; i < numDataBytes; i++) {\r
380               toEncode[i] = dataBytes.at(i);\r
381             }\r
382             new ReedSolomonEncoder(GF256.QR_CODE_FIELD).encode(toEncode, numEcBytesInBlock);\r
383 \r
384             ByteArray ecBytes = new ByteArray(numEcBytesInBlock);\r
385             for (int i = 0; i < numEcBytesInBlock; i++) {\r
386               ecBytes.set(i, toEncode[numDataBytes + i]);\r
387             }\r
388             return ecBytes;\r
389           }\r
390 \r
391           // Append mode info. On success, store the result in "bits" and return true. On error, return\r
392           // false.\r
393           static void appendModeInfo(Mode mode, BitVector bits) {\r
394             bits.appendBits(mode.getBits(), 4);\r
395           }\r
396 \r
397 \r
398           // Append length info. On success, store the result in "bits" and return true. On error, return\r
399           // false.\r
400           static void appendLengthInfo(int numLetters, int version, Mode mode, BitVector bits){\r
401             int numBits = mode.getCharacterCountBits(Version.getVersionForNumber(version));\r
402             if (numLetters > ((1 << numBits) - 1)) {\r
403               throw new WriterException(numLetters + "is bigger than" + ((1 << numBits) - 1));\r
404             }\r
405             bits.appendBits(numLetters, numBits);\r
406           }\r
407 \r
408           // Append "bytes" in "mode" mode (encoding) into "bits". On success, store the result in "bits"\r
409           // and return true.\r
410           static void appendBytes(String content, Mode mode, BitVector bits) {\r
411             if (mode.Equals(Mode.NUMERIC)) {\r
412               appendNumericBytes(content, bits);\r
413             } else if (mode.Equals(Mode.ALPHANUMERIC)) {\r
414               appendAlphanumericBytes(content, bits);\r
415             } else if (mode.Equals(Mode.BYTE)) {\r
416               append8BitBytes(content, bits);\r
417             } else if (mode.Equals(Mode.KANJI)) {\r
418               appendKanjiBytes(content, bits);\r
419             } else {\r
420               throw new WriterException("Invalid mode: " + mode);\r
421             }\r
422           }\r
423 \r
424           static void appendNumericBytes(String content, BitVector bits) {\r
425             int length = content.Length;\r
426             int i = 0;\r
427             while (i < length) {\r
428               int num1 = content[i] - '0';\r
429               if (i + 2 < length) {\r
430                 // Encode three numeric letters in ten bits.\r
431                 int num2 = content[i + 1] - '0';\r
432                 int num3 = content[i + 2] - '0';\r
433                 bits.appendBits(num1 * 100 + num2 * 10 + num3, 10);\r
434                 i += 3;\r
435               } else if (i + 1 < length) {\r
436                 // Encode two numeric letters in seven bits.\r
437                 int num2 = content[i + 1] - '0';\r
438                 bits.appendBits(num1 * 10 + num2, 7);\r
439                 i += 2;\r
440               } else {\r
441                 // Encode one numeric letter in four bits.\r
442                 bits.appendBits(num1, 4);\r
443                 i++;\r
444               }\r
445             }\r
446           }\r
447 \r
448           static void appendAlphanumericBytes(String content, BitVector bits) {\r
449             int length = content.Length;\r
450             int i = 0;\r
451             while (i < length) {\r
452               int code1 = getAlphanumericCode(content[i]);\r
453               if (code1 == -1) {\r
454                 throw new WriterException();\r
455               }\r
456               if (i + 1 < length) {\r
457                 int code2 = getAlphanumericCode(content[i + 1]);\r
458                 if (code2 == -1) {\r
459                   throw new WriterException();\r
460                 }\r
461                 // Encode two alphanumeric letters in 11 bits.\r
462                 bits.appendBits(code1 * 45 + code2, 11);\r
463                 i += 2;\r
464               } else {\r
465                 // Encode one alphanumeric letter in six bits.\r
466                 bits.appendBits(code1, 6);\r
467                 i++;\r
468               }\r
469             }\r
470           }\r
471 \r
472           static void append8BitBytes(String content, BitVector bits) {\r
473             byte[] bytes;\r
474             try {\r
475                 bytes = System.Text.ASCIIEncoding.ASCII.GetBytes("ISO-8859-1");\r
476             } catch (Exception uee) {\r
477               throw new WriterException(uee.ToString());\r
478             }\r
479             for (int i = 0; i < bytes.Length; ++i) {\r
480               bits.appendBits(bytes[i], 8);\r
481             }\r
482           }\r
483 \r
484           static void appendKanjiBytes(String content, BitVector bits) {\r
485             byte[] bytes;\r
486             try {\r
487                 bytes=System.Text.ASCIIEncoding.ASCII.GetBytes("Shift_JIS");\r
488             } catch (Exception uee) {\r
489               throw new WriterException(uee.ToString());\r
490             }\r
491             int length = bytes.Length;\r
492             for (int i = 0; i < length; i += 2) {\r
493               int byte1 = bytes[i] & 0xFF;\r
494               int byte2 = bytes[i + 1] & 0xFF;\r
495               int code = (byte1 << 8) | byte2;\r
496               int subtracted = -1;\r
497               if (code >= 0x8140 && code <= 0x9ffc) {\r
498                 subtracted = code - 0x8140;\r
499               } else if (code >= 0xe040 && code <= 0xebbf) {\r
500                 subtracted = code - 0xc140;\r
501               }\r
502               if (subtracted == -1) {\r
503                 throw new WriterException("Invalid byte sequence");\r
504               }\r
505               int encoded = ((subtracted >> 8) * 0xc0) + (subtracted & 0xff);\r
506               bits.appendBits(encoded, 13);\r
507             }\r
508           }\r
509     \r
510     }\r
511 }