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