1 /**
2  * libdmtx - Data Matrix Encoding/Decoding Library
3  * Copyright 2011 Mike Laughton. All rights reserved.
4  * Copyright 2012-2016 Vadim A. Misbakh-Soloviov. All rights reserved.
5  *
6  * See LICENSE file in the main project directory for full
7  * terms of use and distribution.
8  *
9  * Contact:
10  * Vadim A. Misbakh-Soloviov <dmtx@mva.name>
11  * Mike Laughton <mike@dragonflylogic.com>
12  *
13  * \file dmtxencodeascii.c
14  * \brief ASCII encoding rules
15  */
16 
17 /**
18  * Simple single scheme encoding uses "Normal"
19  * The optimizer needs to track "Expanded" and "Compact" streams separately, so they
20  * are called explicitly.
21  *
22  *   Normal:   Automatically collapses 2 consecutive digits into one codeword
23  *   Expanded: Uses a whole codeword to represent a digit (never collapses)
24  *   Compact:  Collapses 2 digits into a single codeword or marks the stream
25  *             invalid if either values are not digits
26  *
27  * \param stream
28  * \param option [Expanded|Compact|Normal]
29  */
30 static void
EncodeNextChunkAscii(DmtxEncodeStream * stream,int option)31 EncodeNextChunkAscii(DmtxEncodeStream *stream, int option)
32 {
33    DmtxByte v0, v1;
34    DmtxBoolean compactDigits;
35 
36    if(StreamInputHasNext(stream))
37    {
38       v0 = StreamInputAdvanceNext(stream); CHKERR;
39 
40       if((option == DmtxEncodeCompact || option == DmtxEncodeNormal) &&
41             StreamInputHasNext(stream))
42       {
43          v1 = StreamInputPeekNext(stream); CHKERR;
44 
45          /* Check for FNC1 character */
46          if(stream->fnc1 != DmtxUndefined && (int)v1 == stream->fnc1)
47          {
48             v1 = 0;
49             compactDigits = DmtxFalse;
50          }
51          else
52             compactDigits = (ISDIGIT(v0) && ISDIGIT(v1)) ? DmtxTrue : DmtxFalse;
53       }
54       else /* option == DmtxEncodeFull */
55       {
56          v1 = 0;
57          compactDigits = DmtxFalse;
58       }
59 
60       if(compactDigits == DmtxTrue)
61       {
62          /* Two adjacent digit chars: Make peek progress official and encode */
63          StreamInputAdvanceNext(stream); CHKERR;
64          AppendValueAscii(stream, 10 * (v0-'0') + (v1-'0') + 130); CHKERR;
65       }
66       else if(option == DmtxEncodeCompact)
67       {
68          /* Can't compact non-digits */
69          StreamMarkInvalid(stream, DmtxErrorCantCompactNonDigits);
70       }
71       else
72       {
73          /* Encode single ASCII value */
74          if(stream->fnc1 != DmtxUndefined && (int)v0 == stream->fnc1)
75          {
76             /* FNC1 */
77             AppendValueAscii(stream, DmtxValueFNC1); CHKERR;
78          }
79          else if(v0 < 128)
80          {
81             /* Regular ASCII */
82             AppendValueAscii(stream, v0 + 1); CHKERR;
83          }
84          else
85          {
86             /* Extended ASCII */
87             AppendValueAscii(stream, DmtxValueAsciiUpperShift); CHKERR;
88             AppendValueAscii(stream, v0 - 127); CHKERR;
89          }
90       }
91    }
92 }
93 
94 /**
95  * this code is separated from EncodeNextChunkAscii() because it needs to be
96  * called directly elsewhere
97  */
98 static void
AppendValueAscii(DmtxEncodeStream * stream,DmtxByte value)99 AppendValueAscii(DmtxEncodeStream *stream, DmtxByte value)
100 {
101    CHKSCHEME(DmtxSchemeAscii);
102 
103    StreamOutputChainAppend(stream, value); CHKERR;
104    stream->outputChainValueCount++;
105 }
106 
107 /**
108  *
109  *
110  */
111 static void
CompleteIfDoneAscii(DmtxEncodeStream * stream,int sizeIdxRequest)112 CompleteIfDoneAscii(DmtxEncodeStream *stream, int sizeIdxRequest)
113 {
114    int sizeIdx;
115 
116    if(stream->status == DmtxStatusComplete)
117       return;
118 
119    if(!StreamInputHasNext(stream))
120    {
121       sizeIdx = FindSymbolSize(stream->output->length, sizeIdxRequest); CHKSIZE;
122       PadRemainingInAscii(stream, sizeIdx); CHKERR;
123       StreamMarkComplete(stream, sizeIdx);
124    }
125 }
126 
127 /**
128  * Can we just receive a length to pad here? I don't like receiving
129  * sizeIdxRequest (or sizeIdx) this late in the game
130  */
131 static void
PadRemainingInAscii(DmtxEncodeStream * stream,int sizeIdx)132 PadRemainingInAscii(DmtxEncodeStream *stream, int sizeIdx)
133 {
134    int symbolRemaining;
135    DmtxByte padValue;
136 
137    CHKSCHEME(DmtxSchemeAscii);
138    CHKSIZE;
139 
140    symbolRemaining = GetRemainingSymbolCapacity(stream->output->length, sizeIdx);
141 
142    /* First pad character is not randomized */
143    if(symbolRemaining > 0)
144    {
145       padValue = DmtxValueAsciiPad;
146       StreamOutputChainAppend(stream, padValue); CHKERR;
147       symbolRemaining--;
148    }
149 
150    /* All remaining pad characters are randomized based on character position */
151    while(symbolRemaining > 0)
152    {
153       padValue = Randomize253State(DmtxValueAsciiPad, stream->output->length + 1);
154       StreamOutputChainAppend(stream, padValue); CHKERR;
155       symbolRemaining--;
156    }
157 }
158 
159 /**
160  * consider receiving instantiated DmtxByteList instead of the output components
161  */
162 static DmtxByteList
EncodeTmpRemainingInAscii(DmtxEncodeStream * stream,DmtxByte * storage,int capacity,DmtxPassFail * passFail)163 EncodeTmpRemainingInAscii(DmtxEncodeStream *stream, DmtxByte *storage,
164       int capacity, DmtxPassFail *passFail)
165 {
166    DmtxEncodeStream streamAscii;
167    DmtxByteList output = dmtxByteListBuild(storage, capacity);
168 
169    /* Create temporary copy of stream that writes to storage */
170    streamAscii = *stream;
171    streamAscii.currentScheme = DmtxSchemeAscii;
172    streamAscii.outputChainValueCount = 0;
173    streamAscii.outputChainWordCount = 0;
174    streamAscii.reason = NULL;
175    streamAscii.sizeIdx = DmtxUndefined;
176    streamAscii.status = DmtxStatusEncoding;
177    streamAscii.output = &output;
178 
179    while(dmtxByteListHasCapacity(streamAscii.output))
180    {
181       if(StreamInputHasNext(&streamAscii))
182          EncodeNextChunkAscii(&streamAscii, DmtxEncodeNormal); /* No CHKERR */
183       else
184          break;
185    }
186 
187    /*
188     * We stopped encoding before attempting to write beyond output boundary so
189     * any stream errors are truly unexpected. The passFail status indicates
190     * whether output.length can be trusted by the calling function.
191     */
192 
193    if(streamAscii.status == DmtxStatusInvalid || streamAscii.status == DmtxStatusFatal)
194       *passFail = DmtxFail;
195    else
196       *passFail = DmtxPass;
197 
198    return output;
199 }
200 
201 /**
202  * \brief  Randomize 253 state
203  * \param  codewordValue
204  * \param  codewordPosition
205  * \return Randomized value
206  */
207 static DmtxByte
Randomize253State(DmtxByte cwValue,int cwPosition)208 Randomize253State(DmtxByte cwValue, int cwPosition)
209 {
210    int pseudoRandom, tmp;
211 
212    pseudoRandom = ((149 * cwPosition) % 253) + 1;
213    tmp = cwValue + pseudoRandom;
214    if(tmp > 254)
215       tmp -= 254;
216 
217    assert(tmp >= 0 && tmp < 256);
218 
219    return (DmtxByte)tmp;
220 }
221