/* * Copyright 2013 ZXing authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ using System; namespace FastReport.Barcode.Aztec { /// /// Generates Aztec 2D barcodes. /// /// Rustam Abdullaev internal static class Encoder { public const int DEFAULT_EC_PERCENT = 33; // default minimal percentage of error check words public const int DEFAULT_AZTEC_LAYERS = 0; private const int MAX_NB_BITS = 32; private const int MAX_NB_BITS_COMPACT = 4; private static readonly int[] WORD_SIZE = { 4, 6, 6, 8, 8, 8, 8, 8, 8, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12 }; /// /// Encodes the given binary content as an Aztec symbol /// /// input data string /// Aztec symbol matrix with metadata public static AztecCode encode(byte[] data) { return encode(data, DEFAULT_EC_PERCENT, DEFAULT_AZTEC_LAYERS); } /// /// Encodes the given binary content as an Aztec symbol /// /// input data string /// minimal percentage of error check words (According to ISO/IEC 24778:2008, /// a minimum of 23% + 3 words is recommended) /// if non-zero, a user-specified value for the number of layers /// /// Aztec symbol matrix with metadata /// public static AztecCode encode(byte[] data, int minECCPercent, int userSpecifiedLayers) { // High-level encode BitArray bits = new HighLevelEncoder(data).encode(); // stuff bits and choose symbol size int eccBits = bits.Size*minECCPercent/100 + 11; int totalSizeBits = bits.Size + eccBits; bool compact; int layers; int totalBitsInLayer; int wordSize; BitArray stuffedBits; if (userSpecifiedLayers != DEFAULT_AZTEC_LAYERS) { compact = userSpecifiedLayers < 0; layers = Math.Abs(userSpecifiedLayers); if (layers > (compact ? MAX_NB_BITS_COMPACT : MAX_NB_BITS)) { throw new ArgumentException( String.Format("Illegal value {0} for layers", userSpecifiedLayers)); } totalBitsInLayer = TotalBitsInLayer(layers, compact); wordSize = WORD_SIZE[layers]; int usableBitsInLayers = totalBitsInLayer - (totalBitsInLayer%wordSize); stuffedBits = stuffBits(bits, wordSize); if (stuffedBits.Size + eccBits > usableBitsInLayers) { throw new ArgumentException("Data to large for user specified layer"); } if (compact && stuffedBits.Size > wordSize*64) { // Compact format only allows 64 data words, though C4 can hold more words than that throw new ArgumentException("Data to large for user specified layer"); } } else { wordSize = 0; stuffedBits = null; // We look at the possible table sizes in the order Compact1, Compact2, Compact3, // Compact4, Normal4,... Normal(i) for i < 4 isn't typically used since Compact(i+1) // is the same size, but has more data. for (int i = 0;; i++) { if (i > MAX_NB_BITS) { throw new ArgumentException("Data too large for an Aztec code"); } compact = i <= 3; layers = compact ? i + 1 : i; totalBitsInLayer = TotalBitsInLayer(layers, compact); if (totalSizeBits > totalBitsInLayer) { continue; } // [Re]stuff the bits if this is the first opportunity, or if the // wordSize has changed if (wordSize != WORD_SIZE[layers]) { wordSize = WORD_SIZE[layers]; stuffedBits = stuffBits(bits, wordSize); } if (stuffedBits == null) { continue; } int usableBitsInLayers = totalBitsInLayer - (totalBitsInLayer%wordSize); if (compact && stuffedBits.Size > wordSize*64) { // Compact format only allows 64 data words, though C4 can hold more words than that continue; } if (stuffedBits.Size + eccBits <= usableBitsInLayers) { break; } } } BitArray messageBits = generateCheckWords(stuffedBits, totalBitsInLayer, wordSize); // generate mode message int messageSizeInWords = stuffedBits.Size / wordSize; BitArray modeMessage = generateModeMessage(compact, layers, messageSizeInWords); // allocate symbol int baseMatrixSize = compact ? 11 + layers*4 : 14 + layers*4; // not including alignment lines int[] alignmentMap = new int[baseMatrixSize]; int matrixSize; if (compact) { // no alignment marks in compact mode, alignmentMap is a no-op matrixSize = baseMatrixSize; for (int i = 0; i < alignmentMap.Length; i++) { alignmentMap[i] = i; } } else { matrixSize = baseMatrixSize + 1 + 2*((baseMatrixSize/2 - 1)/15); int origCenter = baseMatrixSize/2; int center = matrixSize/2; for (int i = 0; i < origCenter; i++) { int newOffset = i + i/15; alignmentMap[origCenter - i - 1] = center - newOffset - 1; alignmentMap[origCenter + i] = center + newOffset + 1; } } BitMatrix matrix = new BitMatrix(matrixSize); // draw data bits for (int i = 0, rowOffset = 0; i < layers; i++) { int rowSize = compact ? (layers - i)*4 + 9 : (layers - i)*4 + 12; for (int j = 0; j < rowSize; j++) { int columnOffset = j*2; for (int k = 0; k < 2; k++) { if (messageBits[rowOffset + columnOffset + k]) { matrix[alignmentMap[i*2 + k], alignmentMap[i*2 + j]] = true; } if (messageBits[rowOffset + rowSize*2 + columnOffset + k]) { matrix[alignmentMap[i*2 + j], alignmentMap[baseMatrixSize - 1 - i*2 - k]] = true; } if (messageBits[rowOffset + rowSize*4 + columnOffset + k]) { matrix[alignmentMap[baseMatrixSize - 1 - i*2 - k], alignmentMap[baseMatrixSize - 1 - i*2 - j]] = true; } if (messageBits[rowOffset + rowSize*6 + columnOffset + k]) { matrix[alignmentMap[baseMatrixSize - 1 - i*2 - j], alignmentMap[i*2 + k]] = true; } } } rowOffset += rowSize*8; } // draw mode message drawModeMessage(matrix, compact, matrixSize, modeMessage); // draw alignment marks if (compact) { drawBullsEye(matrix, matrixSize/2, 5); } else { drawBullsEye(matrix, matrixSize/2, 7); for (int i = 0, j = 0; i < baseMatrixSize/2 - 1; i += 15, j += 16) { for (int k = (matrixSize/2) & 1; k < matrixSize; k += 2) { matrix[matrixSize/2 - j, k] = true; matrix[matrixSize/2 + j, k] = true; matrix[k, matrixSize/2 - j] = true; matrix[k, matrixSize/2 + j] = true; } } } AztecCode aztec = new AztecCode(); aztec.isCompact = compact; aztec.Size = matrixSize; aztec.Layers = layers; aztec.CodeWords = messageSizeInWords; aztec.Matrix = matrix; return aztec; } private static void drawBullsEye(BitMatrix matrix, int center, int size) { for (int i = 0; i < size; i += 2) { for (int j = center - i; j <= center + i; j++) { matrix[j, center - i] = true; matrix[j, center + i] = true; matrix[center - i, j] = true; matrix[center + i, j] = true; } } matrix[center - size, center - size] = true; matrix[center - size + 1, center - size] = true; matrix[center - size, center - size + 1] = true; matrix[center + size, center - size] = true; matrix[center + size, center - size + 1] = true; matrix[center + size, center + size - 1] = true; } internal static BitArray generateModeMessage(bool compact, int layers, int messageSizeInWords) { BitArray modeMessage = new BitArray(); if (compact) { modeMessage.appendBits(layers - 1, 2); modeMessage.appendBits(messageSizeInWords - 1, 6); modeMessage = generateCheckWords(modeMessage, 28, 4); } else { modeMessage.appendBits(layers - 1, 5); modeMessage.appendBits(messageSizeInWords - 1, 11); modeMessage = generateCheckWords(modeMessage, 40, 4); } return modeMessage; } private static void drawModeMessage(BitMatrix matrix, bool compact, int matrixSize, BitArray modeMessage) { int center = matrixSize / 2; if (compact) { for (int i = 0; i < 7; i++) { int offset = center - 3 + i; if (modeMessage[i]) { matrix[offset, center - 5] = true; } if (modeMessage[i + 7]) { matrix[center + 5, offset] = true; } if (modeMessage[20 - i]) { matrix[offset, center + 5] = true; } if (modeMessage[27 - i]) { matrix[center - 5, offset] = true; } } } else { for (int i = 0; i < 10; i++) { int offset = center - 5 + i + i / 5; if (modeMessage[i]) { matrix[offset, center - 7] = true; } if (modeMessage[i + 10]) { matrix[center + 7, offset] = true; } if (modeMessage[29 - i]) { matrix[offset, center + 7] = true; } if (modeMessage[39 - i]) { matrix[center - 7, offset] = true; } } } } private static BitArray generateCheckWords(BitArray bitArray, int totalBits, int wordSize) { if (bitArray.Size % wordSize != 0) throw new InvalidOperationException("size of bit array is not a multiple of the word size"); // bitArray is guaranteed to be a multiple of the wordSize, so no padding needed int messageSizeInWords = bitArray.Size / wordSize; ReedSolomonEncoder rs = new ReedSolomonEncoder(getGF(wordSize)); int totalWords = totalBits / wordSize; int[] messageWords = bitsToWords(bitArray, wordSize, totalWords); rs.encode(messageWords, totalWords - messageSizeInWords); int startPad = totalBits % wordSize; BitArray messageBits = new BitArray(); messageBits.appendBits(0, startPad); foreach (int messageWord in messageWords) { messageBits.appendBits(messageWord, wordSize); } return messageBits; } private static int[] bitsToWords(BitArray stuffedBits, int wordSize, int totalWords) { int[] message = new int[totalWords]; int i; int n; for (i = 0, n = stuffedBits.Size / wordSize; i < n; i++) { int value = 0; for (int j = 0; j < wordSize; j++) { value |= stuffedBits[i * wordSize + j] ? (1 << wordSize - j - 1) : 0; } message[i] = value; } return message; } private static GenericGF getGF(int wordSize) { switch (wordSize) { case 4: return GenericGF.AZTEC_PARAM; case 6: return GenericGF.AZTEC_DATA_6; case 8: return GenericGF.AZTEC_DATA_8; case 10: return GenericGF.AZTEC_DATA_10; case 12: return GenericGF.AZTEC_DATA_12; default: return null; } } internal static BitArray stuffBits(BitArray bits, int wordSize) { BitArray @out = new BitArray(); int n = bits.Size; int mask = (1 << wordSize) - 2; for (int i = 0; i < n; i += wordSize) { int word = 0; for (int j = 0; j < wordSize; j++) { if (i + j >= n || bits[i + j]) { word |= 1 << (wordSize - 1 - j); } } if ((word & mask) == mask) { @out.appendBits(word & mask, wordSize); i--; } else if ((word & mask) == 0) { @out.appendBits(word | 1, wordSize); i--; } else { @out.appendBits(word, wordSize); } } return @out; } private static int TotalBitsInLayer(int layers, bool compact) { return ((compact ? 88 : 112) + 16 * layers) * layers; } } }