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 dmtxencodec40textx12.c
14  * \brief C40/Text/X12 encoding rules
15  */
16 
17 #undef CHKERR
18 #define CHKERR { if(stream->status != DmtxStatusEncoding) { return; } }
19 
20 #undef CHKSIZE
21 #define CHKSIZE { if(sizeIdx == DmtxUndefined) { StreamMarkInvalid(stream, DmtxErrorUnknown); return; } }
22 
23 #undef CHKPASS
24 #define CHKPASS { if(passFail == DmtxFail) { StreamMarkFatal(stream, DmtxErrorUnknown); return; } }
25 
26 #undef RETURN_IF_FAIL
27 #define RETURN_IF_FAIL { if(*passFail == DmtxFail) return; }
28 
29 /**
30  *
31  *
32  */
33 static void
EncodeNextChunkCTX(DmtxEncodeStream * stream,int sizeIdxRequest)34 EncodeNextChunkCTX(DmtxEncodeStream *stream, int sizeIdxRequest)
35 {
36    int i;
37    DmtxPassFail passFail;
38    DmtxByte inputValue;
39    DmtxByte valueListStorage[6];
40    DmtxByteList valueList = dmtxByteListBuild(valueListStorage, sizeof(valueListStorage));
41 
42    while(StreamInputHasNext(stream))
43    {
44       if(stream->currentScheme == DmtxSchemeX12)
45       {
46           /* Check for FNC1 character */
47           inputValue = StreamInputPeekNext(stream); CHKERR;
48           if(stream->fnc1 != DmtxUndefined && (int)inputValue == stream->fnc1) {
49              /* X12 does not allow partial blocks, resend last 1 or 2 as ASCII */
50              EncodeChangeScheme(stream, DmtxSchemeAscii, DmtxUnlatchExplicit); CHKERR;
51              for(i = 0; i < valueList.length % 3; i++)
52                 StreamInputAdvancePrev(stream); CHKERR;
53 
54              while(i) {
55                 inputValue = StreamInputAdvanceNext(stream); CHKERR;
56                 AppendValueAscii(stream, inputValue + 1); CHKERR;
57                 i--;
58              }
59 
60              StreamInputAdvanceNext(stream); CHKERR;
61              AppendValueAscii(stream, DmtxValueFNC1); CHKERR;
62              return;
63           }
64       }
65       inputValue = StreamInputAdvanceNext(stream); CHKERR;
66 
67       /* Expand next input value into up to 4 CTX values and add to valueList */
68       PushCTXValues(&valueList, inputValue, stream->currentScheme, &passFail, stream->fnc1);
69       if(passFail == DmtxFail)
70       {
71          /* XXX Perhaps PushCTXValues should return this error code */
72          StreamMarkInvalid(stream, DmtxErrorUnsupportedCharacter);
73          return;
74       }
75 
76       /* If there at least 3 CTX values available encode them to output */
77       while(valueList.length >= 3)
78       {
79          AppendValuesCTX(stream, &valueList); CHKERR;
80          ShiftValueListBy3(&valueList, &passFail); CHKPASS;
81       }
82 
83       /* Finished on byte boundary -- done with current chunk */
84       if(valueList.length == 0)
85          break;
86    }
87 
88    /*
89     * Special case: If all input values have been consumed and 1 or 2 unwritten
90     * C40/Text/X12 values remain, finish encoding the symbol according to the
91     * established end-of-symbol conditions.
92     */
93    if(!StreamInputHasNext(stream) && valueList.length > 0)
94    {
95       if(stream->currentScheme == DmtxSchemeX12)
96       {
97          CompletePartialX12(stream, &valueList, sizeIdxRequest); CHKERR;
98       }
99       else
100       {
101          CompletePartialC40Text(stream, &valueList, sizeIdxRequest); CHKERR;
102       }
103    }
104 }
105 
106 /**
107  *
108  *
109  */
110 static void
AppendValuesCTX(DmtxEncodeStream * stream,DmtxByteList * valueList)111 AppendValuesCTX(DmtxEncodeStream *stream, DmtxByteList *valueList)
112 {
113    int pairValue;
114    DmtxByte cw0, cw1;
115 
116    if(!IsCTX(stream->currentScheme))
117    {
118       StreamMarkFatal(stream, DmtxErrorUnexpectedScheme);
119       return;
120    }
121 
122    if(valueList->length < 3)
123    {
124       StreamMarkFatal(stream, DmtxErrorIncompleteValueList);
125       return;
126    }
127 
128    /* Build codewords from computed value */
129    pairValue = (1600 * valueList->b[0]) + (40 * valueList->b[1]) + valueList->b[2] + 1;
130    cw0 = pairValue / 256;
131    cw1 = pairValue % 256;
132 
133    /* Append 2 codewords */
134    StreamOutputChainAppend(stream, cw0); CHKERR;
135    StreamOutputChainAppend(stream, cw1); CHKERR;
136 
137    /* Update count for 3 encoded values */
138    stream->outputChainValueCount += 3;
139 }
140 
141 /**
142  *
143  *
144  */
145 static void
AppendUnlatchCTX(DmtxEncodeStream * stream)146 AppendUnlatchCTX(DmtxEncodeStream *stream)
147 {
148    if(!IsCTX(stream->currentScheme))
149    {
150       StreamMarkFatal(stream, DmtxErrorUnexpectedScheme);
151       return;
152    }
153 
154    /* Verify we are on byte boundary */
155    if(stream->outputChainValueCount % 3 != 0)
156    {
157       StreamMarkInvalid(stream, DmtxErrorNotOnByteBoundary);
158       return;
159    }
160 
161    StreamOutputChainAppend(stream, DmtxValueCTXUnlatch); CHKERR;
162 
163    stream->outputChainValueCount++;
164 }
165 
166 /**
167  * Complete C40/Text/X12 encoding if it matches a known end-of-symbol condition.
168  *
169  *   Term  Trip  Symbol  Codeword
170  *   Cond  Size  Remain  Sequence
171  *   ----  ----  ------  -----------------------
172  *    (a)     3       2  Special case
173  *            -       -  UNLATCH [PAD]
174  */
175 static void
CompleteIfDoneCTX(DmtxEncodeStream * stream,int sizeIdxRequest)176 CompleteIfDoneCTX(DmtxEncodeStream *stream, int sizeIdxRequest)
177 {
178    int sizeIdx;
179    int symbolRemaining;
180 
181    if(stream->status == DmtxStatusComplete)
182       return;
183 
184    if(!StreamInputHasNext(stream))
185    {
186       sizeIdx = FindSymbolSize(stream->output->length, sizeIdxRequest); CHKSIZE;
187       symbolRemaining = GetRemainingSymbolCapacity(stream->output->length, sizeIdx);
188 
189       if(symbolRemaining > 0)
190       {
191          EncodeChangeScheme(stream, DmtxSchemeAscii, DmtxUnlatchExplicit); CHKERR;
192          PadRemainingInAscii(stream, sizeIdx);
193       }
194 
195       StreamMarkComplete(stream, sizeIdx);
196    }
197 }
198 
199 /**
200  * The remaining values can exist in 3 possible cases:
201  *
202  *   a) 1 C40/Text/X12 remaining == 1 data
203  *   b) 2 C40/Text/X12 remaining == 1 shift + 1 data
204  *   c) 2 C40/Text/X12 remaining == 1 data +  1 data
205  *
206  * To distinguish between cases (b) and (c), encode the final input value to
207  * C40/Text/X12 in a temporary location and check the resulting length. If
208  * it expands to multiple values it represents (b); otherwise it is (c). This
209  * accounts for both shift and upper shift conditions.
210  *
211  * Note that in cases (a) and (c) the final C40/Text/X12 value encoded in the
212  * previous chunk may have been a shift value, but this will be ignored by
213  * the decoder due to the implicit shift to ASCII. <-- what if symbol is much
214  * larger though?
215  *
216  *   Term    Value  Symbol  Codeword
217  *   Cond    Count  Remain  Sequence
218  *   ----  -------  ------  ------------------------
219  *    (b)    C40 2       2  C40+C40+0
220  *    (d)  ASCII 1       1  ASCII (implicit unlatch)
221  *    (c)  ASCII 1       2  UNLATCH ASCII
222  *               -       -  UNLATCH (finish ASCII)
223  */
224 static void
CompletePartialC40Text(DmtxEncodeStream * stream,DmtxByteList * valueList,int sizeIdxRequest)225 CompletePartialC40Text(DmtxEncodeStream *stream, DmtxByteList *valueList, int sizeIdxRequest)
226 {
227    int i;
228    int sizeIdx1, sizeIdx2;
229    int symbolRemaining1, symbolRemaining2;
230    DmtxPassFail passFail;
231    DmtxByte inputValue;
232    DmtxByte outputTmpStorage[4];
233    DmtxByteList outputTmp = dmtxByteListBuild(outputTmpStorage, sizeof(outputTmpStorage));
234 
235    if(stream->currentScheme != DmtxSchemeC40 && stream->currentScheme != DmtxSchemeText)
236    {
237       StreamMarkFatal(stream, DmtxErrorUnexpectedScheme);
238       return;
239    }
240 
241    /* Should have exactly one or two input values left */
242    assert(valueList->length == 1 || valueList->length == 2);
243 
244    sizeIdx1 = FindSymbolSize(stream->output->length + 1, sizeIdxRequest);
245    sizeIdx2 = FindSymbolSize(stream->output->length + 2, sizeIdxRequest);
246 
247    symbolRemaining1 = GetRemainingSymbolCapacity(stream->output->length, sizeIdx1);
248    symbolRemaining2 = GetRemainingSymbolCapacity(stream->output->length, sizeIdx2);
249 
250    if(valueList->length == 2 && symbolRemaining2 == 2)
251    {
252       /* End of symbol condition (b) -- Use Shift1 to pad final list value */
253       dmtxByteListPush(valueList, DmtxValueCTXShift1, &passFail); CHKPASS;
254       AppendValuesCTX(stream, valueList); CHKERR;
255       StreamMarkComplete(stream, sizeIdx2);
256    }
257    else
258    {
259       /*
260        * Rollback progress of previously consumed input value(s) since ASCII
261        * encoder will be used to finish the symbol. 2 rollbacks are needed if
262        * valueList holds 2 data words (i.e., not shifts or upper shifts).
263        */
264 
265       StreamInputAdvancePrev(stream); CHKERR;
266       inputValue = StreamInputPeekNext(stream); CHKERR;
267 
268       /* Test-encode most recently consumed input value to C40/Text/X12 */
269       PushCTXValues(&outputTmp, inputValue, stream->currentScheme, &passFail, stream->fnc1);
270       if(valueList->length == 2 && outputTmp.length == 1)
271          StreamInputAdvancePrev(stream); CHKERR;
272 
273       /* Re-use outputTmp to hold ASCII representation of 1-2 input values */
274       /* XXX Refactor how the DmtxByteList is passed back here */
275       outputTmp = EncodeTmpRemainingInAscii(stream, outputTmpStorage,
276             sizeof(outputTmpStorage), &passFail);
277 
278       if(passFail == DmtxFail)
279       {
280          StreamMarkFatal(stream, DmtxErrorUnknown);
281          return;
282       }
283 
284       if(outputTmp.length == 1 && symbolRemaining1 == 1)
285       {
286          /* End of symbol condition (d) */
287          EncodeChangeScheme(stream, DmtxSchemeAscii, DmtxUnlatchImplicit); CHKERR;
288          AppendValueAscii(stream, outputTmp.b[0]); CHKERR;
289 
290          /* Register progress since encoding happened outside normal path */
291          stream->inputNext = stream->input->length;
292          StreamMarkComplete(stream, sizeIdx1);
293       }
294       else
295       {
296          /* Finish in ASCII (c) */
297          EncodeChangeScheme(stream, DmtxSchemeAscii, DmtxUnlatchExplicit); CHKERR;
298          for(i = 0; i < outputTmp.length; i++)
299             AppendValueAscii(stream, outputTmp.b[i]); CHKERR;
300 
301          sizeIdx1 = FindSymbolSize(stream->output->length, sizeIdxRequest);
302          PadRemainingInAscii(stream, sizeIdx1);
303 
304          /* Register progress since encoding happened outside normal path */
305          stream->inputNext = stream->input->length;
306          StreamMarkComplete(stream, sizeIdx1);
307       }
308    }
309 }
310 
311 /**
312  * Partial chunks are not valid in X12. Encode using ASCII instead, using
313  * an implied unlatch if there is exactly one ascii codeword and one symbol
314  * codeword remaining. Otherwise use explicit unlatch.
315  */
316 static void
CompletePartialX12(DmtxEncodeStream * stream,DmtxByteList * valueList,int sizeIdxRequest)317 CompletePartialX12(DmtxEncodeStream *stream, DmtxByteList *valueList, int sizeIdxRequest)
318 {
319    int i;
320    int sizeIdx;
321    int symbolRemaining;
322    DmtxPassFail passFail;
323    DmtxByte outputTmpStorage[2];
324    DmtxByteList outputTmp;
325 
326    if(stream->currentScheme != DmtxSchemeX12)
327    {
328       StreamMarkFatal(stream, DmtxErrorUnexpectedScheme);
329       return;
330    }
331 
332    /* Should have exactly one or two input values left */
333    assert(valueList->length == 1 || valueList->length == 2);
334 
335    /* Roll back input progress */
336    for(i = 0; i < valueList->length; i++)
337    {
338       StreamInputAdvancePrev(stream); CHKERR;
339    }
340 
341    /* Encode up to 2 codewords to a temporary stream */
342    outputTmp = EncodeTmpRemainingInAscii(stream, outputTmpStorage,
343          sizeof(outputTmpStorage), &passFail);
344 
345    sizeIdx = FindSymbolSize(stream->output->length + 1, sizeIdxRequest);
346    symbolRemaining = GetRemainingSymbolCapacity(stream->output->length, sizeIdx);
347 
348    if(outputTmp.length == 1 && symbolRemaining == 1)
349    {
350       /* End of symbol condition (XXX) */
351       EncodeChangeScheme(stream, DmtxSchemeAscii, DmtxUnlatchImplicit); CHKERR;
352       AppendValueAscii(stream, outputTmp.b[0]); CHKERR;
353 
354       /* Register progress since encoding happened outside normal path */
355       stream->inputNext = stream->input->length;
356       StreamMarkComplete(stream, sizeIdx);
357    }
358    else
359    {
360       /* Finish in ASCII (XXX) */
361       EncodeChangeScheme(stream, DmtxSchemeAscii, DmtxUnlatchExplicit); CHKERR;
362       for(i = 0; i < outputTmp.length; i++)
363          AppendValueAscii(stream, outputTmp.b[i]); CHKERR;
364 
365       sizeIdx = FindSymbolSize(stream->output->length, sizeIdxRequest);
366       PadRemainingInAscii(stream, sizeIdx);
367 
368       /* Register progress since encoding happened outside normal path */
369       stream->inputNext = stream->input->length;
370       StreamMarkComplete(stream, sizeIdx);
371    }
372 }
373 
374 /**
375  * Return DmtxTrue 1 or 2 X12 values remain, otherwise DmtxFalse
376  */
377 static DmtxBoolean
PartialX12ChunkRemains(DmtxEncodeStream * stream)378 PartialX12ChunkRemains(DmtxEncodeStream *stream)
379 {
380    DmtxEncodeStream streamTmp;
381    DmtxByte inputValue;
382    DmtxByte valueListStorage[6];
383    DmtxByteList valueList = dmtxByteListBuild(valueListStorage, sizeof(valueListStorage));
384    DmtxPassFail passFail;
385 
386    /* Create temporary copy of stream to track test input progress */
387    streamTmp = *stream;
388    streamTmp.currentScheme = DmtxSchemeX12;
389    streamTmp.outputChainValueCount = 0;
390    streamTmp.outputChainWordCount = 0;
391    streamTmp.reason = NULL;
392    streamTmp.sizeIdx = DmtxUndefined;
393    streamTmp.status = DmtxStatusEncoding;
394    streamTmp.output = NULL;
395 
396    while(StreamInputHasNext(&streamTmp))
397    {
398       inputValue = StreamInputAdvanceNext(&streamTmp);
399       if(stream->status != DmtxStatusEncoding)
400       {
401          StreamMarkInvalid(stream, DmtxErrorUnknown);
402          return DmtxFalse;
403       }
404 
405       /* Expand next input value into up to 4 CTX values and add to valueList */
406       PushCTXValues(&valueList, inputValue, streamTmp.currentScheme, &passFail, stream->fnc1);
407       if(passFail == DmtxFail)
408       {
409          StreamMarkInvalid(stream, DmtxErrorUnknown);
410          return DmtxFalse;
411       }
412 
413       /* Not a final partial chunk */
414       if(valueList.length >= 3)
415          return DmtxFalse;
416    }
417 
418    return (valueList.length == 0) ? DmtxFalse : DmtxTrue;
419 }
420 
421 /**
422  *
423  *
424  */
425 static void
PushCTXValues(DmtxByteList * valueList,DmtxByte inputValue,int targetScheme,DmtxPassFail * passFail,int fnc1)426 PushCTXValues(DmtxByteList *valueList, DmtxByte inputValue, int targetScheme,
427       DmtxPassFail *passFail, int fnc1)
428 {
429    assert(valueList->length <= 2);
430 
431    /* Handle extended ASCII with Upper Shift character */
432    if(inputValue > 127 && (fnc1 == DmtxUndefined || (int)inputValue != fnc1))
433    {
434       if(targetScheme == DmtxSchemeX12)
435       {
436          *passFail = DmtxFail;
437          return;
438       }
439       else
440       {
441          dmtxByteListPush(valueList, DmtxValueCTXShift2, passFail); RETURN_IF_FAIL;
442          dmtxByteListPush(valueList, 30, passFail); RETURN_IF_FAIL;
443          inputValue -= 128;
444       }
445    }
446 
447    /* Handle all other characters according to encodation scheme */
448    if(targetScheme == DmtxSchemeX12)
449    {
450       if(inputValue == 13)
451       {
452          dmtxByteListPush(valueList, 0, passFail); RETURN_IF_FAIL;
453       }
454       else if(inputValue == 42)
455       {
456          dmtxByteListPush(valueList, 1, passFail); RETURN_IF_FAIL;
457       }
458       else if(inputValue == 62)
459       {
460          dmtxByteListPush(valueList, 2, passFail); RETURN_IF_FAIL;
461       }
462       else if(inputValue == 32)
463       {
464          dmtxByteListPush(valueList, 3, passFail); RETURN_IF_FAIL;
465       }
466       else if(inputValue >= 48 && inputValue <= 57)
467       {
468          dmtxByteListPush(valueList, inputValue - 44, passFail); RETURN_IF_FAIL;
469       }
470       else if(inputValue >= 65 && inputValue <= 90)
471       {
472          dmtxByteListPush(valueList, inputValue - 51, passFail); RETURN_IF_FAIL;
473       }
474       else
475       {
476          *passFail = DmtxFail;
477          return;
478       }
479    }
480    else
481    {
482       /* targetScheme is C40 or Text */
483 
484       /* Check for FNC1 character */
485       if(fnc1 != DmtxUndefined && (int)inputValue == fnc1)
486       {
487          dmtxByteListPush(valueList, DmtxValueCTXShift2, passFail); RETURN_IF_FAIL;
488          dmtxByteListPush(valueList, 27, passFail); RETURN_IF_FAIL; /* C40 version of FNC1 */
489       }
490       else if(inputValue <= 31)
491       {
492          dmtxByteListPush(valueList, DmtxValueCTXShift1, passFail); RETURN_IF_FAIL;
493          dmtxByteListPush(valueList, inputValue, passFail); RETURN_IF_FAIL;
494       }
495       else if(inputValue == 32)
496       {
497          dmtxByteListPush(valueList, 3, passFail); RETURN_IF_FAIL;
498       }
499       else if(inputValue <= 47)
500       {
501          dmtxByteListPush(valueList, DmtxValueCTXShift2, passFail); RETURN_IF_FAIL;
502          dmtxByteListPush(valueList, inputValue - 33, passFail); RETURN_IF_FAIL;
503       }
504       else if(inputValue <= 57)
505       {
506          dmtxByteListPush(valueList, inputValue - 44, passFail); RETURN_IF_FAIL;
507       }
508       else if(inputValue <= 64)
509       {
510          dmtxByteListPush(valueList, DmtxValueCTXShift2, passFail); RETURN_IF_FAIL;
511          dmtxByteListPush(valueList, inputValue - 43, passFail); RETURN_IF_FAIL;
512       }
513       else if(inputValue <= 90 && targetScheme == DmtxSchemeC40)
514       {
515          dmtxByteListPush(valueList, inputValue - 51, passFail); RETURN_IF_FAIL;
516       }
517       else if(inputValue <= 90 && targetScheme == DmtxSchemeText)
518       {
519          dmtxByteListPush(valueList, DmtxValueCTXShift3, passFail); RETURN_IF_FAIL;
520          dmtxByteListPush(valueList, inputValue - 64, passFail); RETURN_IF_FAIL;
521       }
522       else if(inputValue <= 95)
523       {
524          dmtxByteListPush(valueList, DmtxValueCTXShift2, passFail); RETURN_IF_FAIL;
525          dmtxByteListPush(valueList, inputValue - 69, passFail); RETURN_IF_FAIL;
526       }
527       else if(inputValue == 96 && targetScheme == DmtxSchemeText)
528       {
529          dmtxByteListPush(valueList, DmtxValueCTXShift3, passFail); RETURN_IF_FAIL;
530          dmtxByteListPush(valueList, 0, passFail); RETURN_IF_FAIL;
531       }
532       else if(inputValue <= 122 && targetScheme == DmtxSchemeText)
533       {
534          dmtxByteListPush(valueList, inputValue - 83, passFail); RETURN_IF_FAIL;
535       }
536       else if(inputValue <= 127)
537       {
538          dmtxByteListPush(valueList, DmtxValueCTXShift3, passFail); RETURN_IF_FAIL;
539          dmtxByteListPush(valueList, inputValue - 96, passFail); RETURN_IF_FAIL;
540       }
541       else
542       {
543          *passFail = DmtxFail;
544          return;
545       }
546    }
547 
548    *passFail = DmtxPass;
549 }
550 
551 /**
552  *
553  *
554  */
555 static DmtxBoolean
IsCTX(int scheme)556 IsCTX(int scheme)
557 {
558    DmtxBoolean isCTX;
559 
560    if(scheme == DmtxSchemeC40 || scheme == DmtxSchemeText || scheme == DmtxSchemeX12)
561       isCTX = DmtxTrue;
562    else
563       isCTX = DmtxFalse;
564 
565    return isCTX;
566 }
567 
568 /**
569  *
570  *
571  */
572 static void
ShiftValueListBy3(DmtxByteList * list,DmtxPassFail * passFail)573 ShiftValueListBy3(DmtxByteList *list, DmtxPassFail *passFail)
574 {
575    int i;
576 
577    /* Shift values */
578    for(i = 0; i < list->length - 3; i++)
579       list->b[i] = list->b[i+3];
580 
581    /* Shorten list by 3 (or less) */
582    for(i = 0; i < 3; i++)
583    {
584       dmtxByteListPop(list, passFail);
585       if(*passFail == DmtxFail)
586          return;
587 
588       if(list->length == 0)
589          break;
590    }
591 
592    *passFail = DmtxPass;
593 }
594