/* code1.c - USS Code One */

/*
    libzint - the open source barcode library
    Copyright (C) 2009-2021 Robin Stuart <rstuart114@gmail.com>

    Redistribution and use in source and binary forms, with or without
    modification, are permitted provided that the following conditions
    are met:

    1. Redistributions of source code must retain the above copyright
       notice, this list of conditions and the following disclaimer.
    2. Redistributions in binary form must reproduce the above copyright
       notice, this list of conditions and the following disclaimer in the
       documentation and/or other materials provided with the distribution.
    3. Neither the name of the project nor the names of its contributors
       may be used to endorse or promote products derived from this software
       without specific prior written permission.

    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
    ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
    ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
    FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
    DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
    OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
    HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
    OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
    SUCH DAMAGE.
 */
/* vim: set ts=4 sw=4 et : */

#include "common.h"
#include "code1.h"
#include "reedsol.h"
#include "large.h"
#include <stdio.h>
#include <math.h>
#ifdef _MSC_VER
#include <malloc.h>
#endif

/* Add solid bar */
static void horiz(struct zint_symbol *symbol, const int row_no, const int full) {
    int i;

    if (full) {
        for (i = 0; i < symbol->width; i++) {
            set_module(symbol, row_no, i);
        }
    } else {
        for (i = 1; i < symbol->width - 1; i++) {
            set_module(symbol, row_no, i);
        }
    }
}

/* Central recognition pattern for Versions A-H */
static void central_finder(struct zint_symbol *symbol, const int start_row, const int row_count,
            const int full_rows) {
    int i;

    for (i = 0; i < row_count; i++) {
        if (i < full_rows) {
            horiz(symbol, start_row + (i * 2), 1);
        } else {
            horiz(symbol, start_row + (i * 2), 0);
            if (i != row_count - 1) {
                set_module(symbol, start_row + (i * 2) + 1, 1);
                set_module(symbol, start_row + (i * 2) + 1, symbol->width - 2);
            }
        }
    }
}

/* Add solid column */
static void vert(struct zint_symbol *symbol, const int column, const int height, const int top) {
    int i;

    if (top) {
        for (i = 0; i < height; i++) {
            set_module(symbol, i, column);
        }
    } else {
        for (i = 0; i < height; i++) {
            set_module(symbol, symbol->rows - i - 1, column);
        }
    }
}

/* Add bump to the right of the vertical recognition pattern column (Versions A-H) */
static void spigot(struct zint_symbol *symbol, const int row_no) {
    int i;

    for (i = symbol->width - 1; i > 0; i--) {
        if (module_is_set(symbol, row_no, i - 1)) {
            set_module(symbol, row_no, i);
        }
    }
}

/* Is basic (non-shifted) C40? */
static int isc40(const unsigned char input) {
    if ((input >= '0' && input <= '9') || (input >= 'A' && input <= 'Z') || input == ' ') {
        return 1;
    }
    return 0;
}

/* Is basic (non-shifted) TEXT? */
static int istext(const unsigned char input) {
    if ((input >= '0' && input <= '9') || (input >= 'a' && input <= 'z') || input == ' ') {
        return 1;
    }
    return 0;
}

/* Is basic (non-shifted) C40/TEXT? */
static int isc40text(const int current_mode, const unsigned char input) {
    return current_mode == C1_C40 ? isc40(input) : istext(input);
}

/* EDI characters are uppercase alphanumerics plus space plus EDI terminator (CR) plus 2 EDI separator chars */
static int isedi(const unsigned char input) {

    if (isc40(input)) {
        return 1;
    }
    if (input == 13 || input == '*' || input == '>') {
        return 1;
    }

    return 0;
}

/* Whether Step Q4bi applies, i.e. if one of the 3 EDI terminator/separator chars appears before a non-EDI char */
static int is_step_Q4bi_applicable(const unsigned char source[], const int sourcelen, const int position) {
    int i;

    for (i = position; i < sourcelen && isedi(source[i]); i++) {
        if (source[i] == 13 || source[i] == '*' || source[i] == '>') {
            return 1;
        }
    }

    return 0;
}

/* Character counts are multiplied by this, so as to be whole integer divisible by 2 and 3 */
#define C1_MULT             6

#define C1_MULT_1_DIV_2     3
#define C1_MULT_2_DIV_3     4
#define C1_MULT_1           6
#define C1_MULT_4_DIV_3     8
#define C1_MULT_2           12
#define C1_MULT_8_DIV_3     16
#define C1_MULT_3           18
#define C1_MULT_10_DIV_3    20
#define C1_MULT_13_DIV_3    26

#define C1_MULT_MINUS_1     5
#define C1_MULT_CEIL(n)     ((((n) + C1_MULT_MINUS_1) / C1_MULT) * C1_MULT)

/* AIM USS Code One Annex D Steps J-R */
static int c1_look_ahead_test(const unsigned char source[], const int sourcelen, const int position,
            const int current_mode, const int gs1) {
    int ascii_count, c40_count, text_count, edi_count, byte_count;
    int ascii_rnded, c40_rnded, text_rnded, edi_rnded, byte_rnded;
    int cnt_1;
    int sp;

    /* Step J1 */
    if (current_mode == C1_ASCII) {
        ascii_count = 0;
        c40_count = C1_MULT_1;
        text_count = C1_MULT_1;
        edi_count = C1_MULT_1;
        byte_count = C1_MULT_2;
    } else {
        ascii_count = C1_MULT_1;
        c40_count = C1_MULT_2;
        text_count = C1_MULT_2;
        edi_count = C1_MULT_2;
        byte_count = C1_MULT_3;
    }

    switch (current_mode) {
        case C1_C40: c40_count = 0; /* Step J2 */
            break;
        case C1_TEXT: text_count = 0; /* Step J3 */
            break;
        case C1_EDI: edi_count = 0; /* Missing in spec */
            break;
        case C1_BYTE: byte_count = 0; /* Step J4 */
            break;
    }

    for (sp = position; sp < sourcelen; sp++) {
        unsigned char c = source[sp];
        int is_extended = c & 0x80;

        /* Step L */
        if ((c >= '0') && (c <= '9')) {
            ascii_count += C1_MULT_1_DIV_2; /* Step L1 */
        } else {
            if (is_extended) {
                ascii_count = ceilf(ascii_count) + C1_MULT_2; /* Step L2 */
            } else {
                ascii_count = ceilf(ascii_count) + C1_MULT_1; /* Step L3 */
            }
        }

        /* Step M */
        if (isc40(c)) {
            c40_count += C1_MULT_2_DIV_3; /* Step M1 */
        } else if (is_extended) {
            c40_count += C1_MULT_8_DIV_3; /* Step M2 */
        } else {
            c40_count += C1_MULT_4_DIV_3; /* Step M3 */
        }

        /* Step N */
        if (istext(c)) {
            text_count += C1_MULT_2_DIV_3; /* Step N1 */
        } else if (is_extended) {
            text_count += C1_MULT_8_DIV_3; /* Step N2 */
        } else {
            text_count += C1_MULT_4_DIV_3; /* Step N3 */
        }

        /* Step O */
        if (isedi(c)) {
            edi_count += C1_MULT_2_DIV_3; /* Step O1 */
        } else if (is_extended) {
            edi_count += C1_MULT_13_DIV_3; /* Step O2 */
        } else {
            edi_count += C1_MULT_10_DIV_3; /* Step O3 */
        }

        /* Step P */
        if (gs1 && (c == '[')) {
            byte_count += C1_MULT_3; /* Step P1 */
        } else {
            byte_count += C1_MULT_1; /* Step P2 */
        }

        /* If at least 4 characters processed */
        /* NOTE: different than spec, where it's at least 3, but that ends up suppressing C40/TEXT/EDI.
           BWIPP also uses 4 (cf very similar Data Matrix ISO/IEC 16022:2006 Annex P algorithm) */
        if (sp >= position + 3) {
            /* Step Q */
            ascii_rnded = C1_MULT_CEIL(ascii_count);
            c40_rnded = C1_MULT_CEIL(c40_count);
            text_rnded = C1_MULT_CEIL(text_count);
            edi_rnded = C1_MULT_CEIL(edi_count);
            byte_rnded = C1_MULT_CEIL(byte_count);

            cnt_1 = byte_count + C1_MULT_1;
            if (cnt_1 <= ascii_rnded && cnt_1 <= c40_rnded && cnt_1 <= text_rnded && cnt_1 <= edi_rnded) {
                return C1_BYTE; /* Step Q1 */
            }
            cnt_1 = ascii_count + C1_MULT_1;
            if (cnt_1 <= c40_rnded && cnt_1 <= text_rnded && cnt_1 <= edi_rnded && cnt_1 <= byte_rnded) {
                return C1_ASCII; /* Step Q2 */
            }
            cnt_1 = text_rnded + C1_MULT_1;
            if (cnt_1 <= ascii_rnded && cnt_1 <= c40_rnded && cnt_1 <= edi_rnded && cnt_1 <= byte_rnded) {
                return C1_TEXT; /* Step Q3 */
            }
            cnt_1 = c40_rnded + C1_MULT_1;
            if (cnt_1 <= ascii_rnded && cnt_1 <= text_rnded) {
                /* Step Q4 */
                if (c40_rnded < edi_rnded) {
                    return C1_C40; /* Step Q4a */
                }
                if (c40_rnded == edi_rnded) {
                    /* Step Q4b */
                    if (is_step_Q4bi_applicable(source, sourcelen, sp + 1)) {
                        return C1_EDI; /* Step Q4bi */
                    }
                    return C1_C40; /* Step Q4bii */
                }
            }
            cnt_1 = edi_rnded + C1_MULT_1;
            if (cnt_1 <= ascii_rnded && cnt_1 <= c40_rnded && cnt_1 <= text_rnded && cnt_1 <= byte_rnded) {
                return C1_EDI; /* Step Q5 */
            }
        }
    }

    /* Step K */
    ascii_rnded = C1_MULT_CEIL(ascii_count);
    c40_rnded = C1_MULT_CEIL(c40_count);
    text_rnded = C1_MULT_CEIL(text_count);
    edi_rnded = C1_MULT_CEIL(edi_count);
    byte_rnded = C1_MULT_CEIL(byte_count);

    if (byte_count <= ascii_rnded && byte_count <= c40_rnded && byte_count <= text_rnded && byte_count <= edi_rnded) {
        return C1_BYTE; /* Step K1 */
    }
    if (ascii_count <= c40_rnded && ascii_count <= text_rnded && ascii_count <= edi_rnded
            && ascii_count <= byte_rnded) {
        return C1_ASCII; /* Step K2 */
    }
    if (c40_rnded <= text_rnded && c40_rnded <= edi_rnded) {
        return C1_C40; /* Step K3 */
    }
    if (text_rnded <= edi_rnded) {
        return C1_TEXT; /* Step K4 */
    }

    return C1_EDI; /* Step K5 */
}

/* Whether can fit last character or characters in a single ASCII codeword */
static int is_last_single_ascii(const unsigned char string[], const int length, const int sp) {
    if (length - sp == 1 && string[sp] <= 127) {
        return 1;
    }
    if (length - sp == 2 && istwodigits(string, length, sp)) {
        return 1;
    }
    return 0;
}

/* Initialize number of digits array (taken from BWIPP) */
static void set_num_digits(const unsigned char source[], const int length, int *num_digits) {
    int i;
    for (i = length - 1; i >= 0; i--) {
        if (source[i] >= '0' && source[i] <= '9') {
            num_digits[i] = num_digits[i + 1] + 1;
        }
    }
}

/* Copy C40/TEXT/EDI triplets from buffer to target. Returns elements left in buffer (< 3) */
static int cte_buffer_transfer(int cte_buffer[6], int cte_p, unsigned target[], int *p_tp) {
    int cte_i, cte_e;
    int tp = *p_tp;

    cte_e = (cte_p / 3) * 3;

    for (cte_i = 0; cte_i < cte_e; cte_i += 3) {
        int iv = (1600 * cte_buffer[cte_i]) + (40 * cte_buffer[cte_i + 1]) + (cte_buffer[cte_i + 2]) + 1;
        target[tp++] = iv >> 8;
        target[tp++] = iv & 0xFF;
    }

    cte_p -= cte_e;

    if (cte_p) {
        memmove(cte_buffer, cte_buffer + cte_e, sizeof(int) * cte_p);
    }

    *p_tp = tp;

    return cte_p;
}

/* Copy DECIMAL bytes to target. Returns bits left in buffer (< 8) */
static int decimal_binary_transfer(char decimal_binary[24], int db_p, unsigned int target[], int *p_tp) {
    int b_i, b_e, p;
    int tp = *p_tp;

    /* Transfer full bytes to target */
    b_e = db_p & 0xF8;

    for (b_i = 0; b_i < b_e; b_i += 8) {
        int value = 0;
        for (p = 0; p < 8; p++) {
            value <<= 1;
            value += decimal_binary[b_i + p] == '1';
        }
        target[tp++] = value;
    }

    db_p &= 0x07; /* Bits remaining */

    if (db_p) {
        memmove(decimal_binary, decimal_binary + b_e, db_p);
    }

    *p_tp = tp;

    return db_p;
}

/* Unlatch to ASCII from DECIMAL mode using 6 ones flag. DECIMAL binary buffer will be empty */
static int decimal_unlatch(char decimal_binary[24], int db_p, unsigned int target[], int *p_tp,
            const int decimal_count, const unsigned char *source, int *p_sp) {
    int sp = *p_sp;
    int bits_left;

    db_p = bin_append_posn(63, 6, decimal_binary, db_p); /* Unlatch */
    if (db_p >= 8) {
        db_p = decimal_binary_transfer(decimal_binary, db_p, target, p_tp);
    }
    bits_left = (8 - db_p) & 0x07;
    if (decimal_count >= 1 && bits_left >= 4) {
        db_p = bin_append_posn(ctoi(source[sp]) + 1, 4, decimal_binary, db_p);
        sp++;
        if (bits_left == 6) {
            db_p = bin_append_posn(1, 2, decimal_binary, db_p);
        }
        (void) decimal_binary_transfer(decimal_binary, db_p, target, p_tp);

    } else if (bits_left) {
        if (bits_left >= 4) {
            db_p = bin_append_posn(15, 4, decimal_binary, db_p);
        }
        if (bits_left == 2 || bits_left == 6) {
            db_p = bin_append_posn(1, 2, decimal_binary, db_p);
        }
        (void) decimal_binary_transfer(decimal_binary, db_p, target, p_tp);
    }

    *p_sp = sp;

    return 0;
}

/* Number of codewords remaining in a particular version (may be negative) */
static int codewords_remaining(struct zint_symbol *symbol, const int tp) {
    int i;

    if (symbol->option_2 == 10) { /* Version T */
        if (tp > 24) {
            return 38 - tp;
        }
        if (tp > 10) {
            return 24 - tp;
        }
        return 10 - tp;
    }
    /* Versions A to H */
    for (i = 6; i >= 0; i--) {
        if (tp > c1_data_length[i]) {
            return c1_data_length[i + 1] - tp;
        }
    }
    return c1_data_length[0] - tp;
}

/* Number of C40/TEXT elements needed to encode `input` */
static int c40text_cnt(const int current_mode, const int gs1, unsigned char input) {
    int cnt;

    if (gs1 && input == '[') {
        return 2;
    }
    cnt = 1;
    if ((current_mode == C1_C40 && c40_shift[input]) || (current_mode == C1_TEXT && text_shift[input])) {
        cnt += 1;
    }

    return cnt;
}

/* Copy `source` to `eci_buf` with "\NNNNNN" ECI indicator at start and backslashes escaped */
static void eci_escape(const int eci, unsigned char *source, const int length, unsigned char *eci_buf,
            const int eci_length) {
    int i, j;

    j = sprintf((char *) eci_buf, "\\%06d", eci);

    for (i = 0; i < length && j < eci_length; i++) {
        if (source[i] == '\\') {
            eci_buf[j++] = '\\';
        }
        eci_buf[j++] = source[i];
    }
    eci_buf[j] = '\0';
}

/* Convert to codewords */
static int c1_encode(struct zint_symbol *symbol, unsigned char source[], unsigned int target[], int length,
            int *p_last_mode) {
    int current_mode, next_mode;
    int sp, tp, gs1, i;
    int cte_buffer[6], cte_p = 0; /* C1_C40/TEXT/EDI buffer and index */
    char decimal_binary[24]; /* C1_DECIMAL buffer */
    int db_p = 0;
    int byte_start = 0;
    int debug_print = symbol->debug & ZINT_DEBUG_PRINT;
    int eci_length = length + 7 + chr_cnt(source, length, '\\');
#ifndef _MSC_VER
    unsigned char eci_buf[eci_length + 1];
    int num_digits[eci_length + 1];
#else
    unsigned char *eci_buf = (unsigned char *) _alloca(eci_length + 1);
    int *num_digits = (int *) _alloca(sizeof(int) * (eci_length + 1));
#endif

    sp = 0;
    tp = 0;
    memset(num_digits, 0, sizeof(int) * (eci_length + 1));

    /* Step A */
    current_mode = C1_ASCII;
    next_mode = C1_ASCII;

    if ((symbol->input_mode & 0x07) == GS1_MODE) {
        gs1 = 1;
    } else {
        gs1 = 0;
    }
    if (gs1) {
        set_num_digits(source, length, num_digits);
        if (length >= 15 && num_digits[0] >= 15) {
            target[tp++] = 236; /* FNC1 and change to Decimal */
            next_mode = C1_DECIMAL;
        } else if (length >= 7 && num_digits[0] == length) {
            target[tp++] = 236; /* FNC1 and change to Decimal */
            next_mode = C1_DECIMAL;
        } else {
            target[tp++] = 232; /* FNC1 */
        }
        /* Note ignoring ECI if GS1 mode (up to caller to warn) */
    } else {
        if (symbol->eci) {
            target[tp++] = 129; /* Pad */
            target[tp++] = '\\' + 1; /* Escape char */
            eci_escape(symbol->eci, source, length, eci_buf, eci_length);
            source = eci_buf;
            length = eci_length;
        }
        set_num_digits(source, length, num_digits);
    }

    do {
        if (current_mode != next_mode) {
            /* Change mode */
            switch (next_mode) {
                case C1_C40: target[tp++] = 230;
                    break;
                case C1_TEXT: target[tp++] = 239;
                    break;
                case C1_EDI: target[tp++] = 238;
                    break;
                case C1_BYTE: target[tp++] = 231;
                    byte_start = tp;
                    target[tp++] = 0; /* Byte count holder (may be expanded to 2 codewords) */
                    break;
            }
            current_mode = next_mode;
        }

        if (current_mode == C1_ASCII) {
            /* Step B - ASCII encodation */
            next_mode = C1_ASCII;

            if ((length - sp) >= 21 && num_digits[sp] >= 21) {
                /* Step B1 */
                next_mode = C1_DECIMAL;
                db_p = bin_append_posn(15, 4, decimal_binary, db_p);
            } else if ((length - sp) >= 13 && num_digits[sp] == (length - sp)) {
                /* Step B2 */
                next_mode = C1_DECIMAL;
                db_p = bin_append_posn(15, 4, decimal_binary, db_p);
            }

            if (next_mode == C1_ASCII) {
                if (istwodigits(source, length, sp)) {
                    if (debug_print) printf("ASCII double-digits ");

                    /* Step B3 */
                    target[tp++] = (10 * ctoi(source[sp])) + ctoi(source[sp + 1]) + 130;
                    sp += 2;
                } else {
                    if ((gs1) && (source[sp] == '[')) {
                        if (length - (sp + 1) >= 15 && num_digits[sp + 1] >= 15) {
                            /* Step B4 */
                            target[tp++] = 236; /* FNC1 and change to Decimal */
                            sp++;
                            next_mode = C1_DECIMAL;
                        } else if (length - (sp + 1) >= 7 && num_digits[sp + 1] == length - (sp + 1)) {
                            /* Step B5 */
                            target[tp++] = 236; /* FNC1 and change to Decimal */
                            sp++;
                            next_mode = C1_DECIMAL;
                        }
                    }

                    if (next_mode == C1_ASCII) {

                        /* Step B6 */
                        next_mode = c1_look_ahead_test(source, length, sp, current_mode, gs1);

                        if (next_mode == C1_ASCII) {
                            if (debug_print) printf("ASCII ");

                            if (source[sp] & 0x80) {
                                /* Step B7 */
                                target[tp++] = 235; /* FNC4 (Upper Shift) */
                                target[tp++] = (source[sp] - 128) + 1;
                            } else if ((gs1) && (source[sp] == '[')) {
                                /* Step B8 */
                                target[tp++] = 232; /* FNC1 */
                            } else {
                                /* Step B8 */
                                target[tp++] = source[sp] + 1;
                            }
                            sp++;
                        }
                    }
                }
            }

        } else if (current_mode == C1_C40 || current_mode == C1_TEXT) {
            /* Step C/D - C40/TEXT encodation */

            next_mode = current_mode;
            if (cte_p == 0) {
                /* Step C/D1 */
                if ((length - sp) >= 12 && num_digits[sp] >= 12) {
                    /* Step C/D1a */
                    next_mode = C1_ASCII;
                } else if ((length - sp) >= 8 && num_digits[sp] == (length - sp)) {
                    /* Step C/D1b */
                    next_mode = C1_ASCII;
                } else {
                    next_mode = c1_look_ahead_test(source, length, sp, current_mode, gs1);
                }
            }

            if (next_mode != current_mode) {
                /* Step C/D1c */
                target[tp++] = 255; /* Unlatch */
            } else {
                /* Step C/D2 */
                const char *ct_shift, *ct_value;

                if (current_mode == C1_C40) {
                    ct_shift = c40_shift;
                    ct_value = c40_value;
                } else {
                    ct_shift = text_shift;
                    ct_value = text_value;
                }
                if (debug_print) printf(current_mode == C1_C40 ? "C40 " : "TEXT ");

                if (source[sp] & 0x80) {
                    cte_buffer[cte_p++] = 1; /* Shift 2 */
                    cte_buffer[cte_p++] = 30; /* FNC4 (Upper Shift) */
                    if (ct_shift[source[sp] - 128]) {
                        cte_buffer[cte_p++] = ct_shift[source[sp] - 128] - 1;
                    }
                    cte_buffer[cte_p++] = ct_value[source[sp] - 128];
                } else if (gs1 && (source[sp] == '[')) {
                    cte_buffer[cte_p++] = 1; /* Shift 2 */
                    cte_buffer[cte_p++] = 27; /* FNC1 */
                } else {
                    if (ct_shift[source[sp]]) {
                        cte_buffer[cte_p++] = ct_shift[source[sp]] - 1;
                    }
                    cte_buffer[cte_p++] = ct_value[source[sp]];
                }

                if (cte_p >= 3) {
                    cte_p = cte_buffer_transfer(cte_buffer, cte_p, target, &tp);
                }
                sp++;
            }

        } else if (current_mode == C1_EDI) {
            /* Step E - EDI Encodation */

            next_mode = C1_EDI;
            if (cte_p == 0) {
                /* Step E1 */
                if ((length - sp) >= 12 && num_digits[sp] >= 12) {
                    /* Step E1a */
                    next_mode = C1_ASCII;
                } else if ((length - sp) >= 8 && num_digits[sp] == (length - sp)) {
                    /* Step E1b */
                    next_mode = C1_ASCII;
                } else if ((length - sp) < 3 || !isedi(source[sp]) || !isedi(source[sp + 1])
                        || !isedi(source[sp + 2])) {
                    /* Step E1c */
                    /* This ensures ASCII switch if don't have EDI triplet, so cte_p will be zero on loop exit */
                    next_mode = C1_ASCII;
                }
            }

            if (next_mode != C1_EDI) {
                if (is_last_single_ascii(source, length, sp) && codewords_remaining(symbol, tp) == 1) {
                    /* No unlatch needed if data fits as ASCII in last data codeword */
                } else {
                    target[tp++] = 255; /* Unlatch */
                }
            } else {
                /* Step E2 */
                static const char edi_nonalphanum_chars[] = "\015*> ";

                if (debug_print) printf("EDI ");

                if ((source[sp] >= '0') && (source[sp] <= '9')) {
                    cte_buffer[cte_p++] = source[sp] - '0' + 4;
                } else if ((source[sp] >= 'A') && (source[sp] <= 'Z')) {
                    cte_buffer[cte_p++] = source[sp] - 'A' + 14;
                } else {
                    cte_buffer[cte_p++] = posn(edi_nonalphanum_chars, source[sp]);
                }

                if (cte_p >= 3) {
                    cte_p = cte_buffer_transfer(cte_buffer, cte_p, target, &tp);
                }
                sp++;
            }

        } else if (current_mode == C1_DECIMAL) {
            /* Step F - Decimal encodation */

            if (debug_print) printf("DECIMAL ");

            next_mode = C1_DECIMAL;

            if (length - sp < 3) {
                /* Step F1 */
                int bits_left = 8 - db_p;
                int can_ascii = bits_left == 8 && is_last_single_ascii(source, length, sp);
                if (codewords_remaining(symbol, tp) == 1 && (can_ascii || (num_digits[sp] == 1 && bits_left >= 4))) {
                    if (can_ascii) {
                        /* Encode last character or last 2 digits as ASCII */
                        if (istwodigits(source, length, sp)) {
                            target[tp++] = (10 * ctoi(source[sp])) + ctoi(source[sp + 1]) + 130;
                            sp += 2;
                        } else {
                            target[tp++] = source[sp] + 1;
                            sp++;
                        }
                    } else {
                        /* Encode last digit in 4 bits */
                        db_p = bin_append_posn(ctoi(source[sp]) + 1, 4, decimal_binary, db_p);
                        sp++;
                        if (bits_left == 6) {
                            db_p = bin_append_posn(1, 2, decimal_binary, db_p);
                        }
                        db_p = decimal_binary_transfer(decimal_binary, db_p, target, &tp);
                    }
                } else {
                    db_p = decimal_unlatch(decimal_binary, db_p, target, &tp, num_digits[sp], source, &sp);
                    current_mode = C1_ASCII; /* Note need to set current_mode also in case exit loop */
                }
                next_mode = C1_ASCII;

            } else {
                if (num_digits[sp] < 3) {
                    /* Step F2 */
                    db_p = decimal_unlatch(decimal_binary, db_p, target, &tp, num_digits[sp], source, &sp);
                    current_mode = next_mode = C1_ASCII; /* Note need to set current_mode also in case exit loop */
                } else {
                    /* Step F3 */
                    /* There are three digits - convert the value to binary */
                    int value = (100 * ctoi(source[sp])) + (10 * ctoi(source[sp + 1])) + ctoi(source[sp + 2]) + 1;
                    db_p = bin_append_posn(value, 10, decimal_binary, db_p);
                    if (db_p >= 8) {
                        db_p = decimal_binary_transfer(decimal_binary, db_p, target, &tp);
                    }
                    sp += 3;
                }
            }

        } else if (current_mode == C1_BYTE) {
            next_mode = C1_BYTE;

            if (gs1 && (source[sp] == '[')) {
                next_mode = C1_ASCII;
            } else {
                if (source[sp] <= 127) {
                    next_mode = c1_look_ahead_test(source, length, sp, current_mode, gs1);
                }
            }

            if (next_mode != C1_BYTE) {
                /* Update byte field length */
                int byte_count = tp - (byte_start + 1);
                if (byte_count <= 249) {
                    target[byte_start] = byte_count;
                } else {
                    /* Insert extra codeword */
                    memmove(target + byte_start + 2, target + byte_start + 1, sizeof(unsigned int) * byte_count);
                    target[byte_start] = 249 + (byte_count / 250);
                    target[byte_start + 1] = (byte_count % 250);
                    tp++;
                }
            } else {
                if (debug_print) printf("BYTE ");

                target[tp++] = source[sp];
                sp++;
            }
        }

        if (tp > 1480) {
            if (debug_print) printf("\n");
            /* Data is too large for symbol */
            return 0;
        }
    } while (sp < length);

    if (debug_print) {
        printf("\nEnd Current Mode: %d, tp %d, cte_p %d, db_p %d\n", current_mode, tp, cte_p, db_p);
    }

    /* Empty buffers (note cte_buffer will be empty if current_mode C1_EDI) */
    if (current_mode == C1_C40 || current_mode == C1_TEXT) {
        if (cte_p >= 1) {
            int cws_remaining = codewords_remaining(symbol, tp);

            /* Note doing strict interpretation of spec here (same as BWIPP), as now also done in Data Matrix case */
            if (cws_remaining == 1 && cte_p == 1 && isc40text(current_mode, source[sp - 1])) {
                /* 2.2.2.2 "...except when a single symbol character is left at the end before the first
                   error correction character. This single character is encoded in the ASCII code set." */
                target[tp++] = source[sp - 1] + 1; /* As ASCII */
                cte_p = 0;
            } else if (cws_remaining == 2 && cte_p == 2) {
                /* 2.2.2.2 "Two characters may be encoded in C40 mode in the last two data symbol characters of the
                   symbol as two C40 values followed by one of the C40 shift characters." */
                cte_buffer[cte_p++] = 0; /* Shift 0 */
                cte_p = cte_buffer_transfer(cte_buffer, cte_p, target, &tp);
            }
            if (cte_p >= 1) {
                int cnt, total_cnt = 0;
                /* Backtrack to last complete triplet (same technique as BWIPP) */
                while (sp > 0 && cte_p % 3) {
                    sp--;
                    cnt = c40text_cnt(current_mode, gs1, source[sp]);
                    total_cnt += cnt;
                    cte_p -= cnt;
                }
                tp -= (total_cnt / 3) * 2;

                target[tp++] = 255; /* Unlatch */
                for (; sp < length; sp++) {
                    if (istwodigits(source, length, sp)) {
                        target[tp++] = (10 * ctoi(source[sp])) + ctoi(source[sp + 1]) + 130;
                        sp++;
                    } else if (source[sp] & 0x80) {
                        target[tp++] = 235; /* FNC4 (Upper Shift) */
                        target[tp++] = (source[sp] - 128) + 1;
                    } else if ((gs1) && (source[sp] == '[')) {
                        target[tp++] = 232; /* FNC1 */
                    } else {
                        target[tp++] = source[sp] + 1;
                    }
                }
                current_mode = C1_ASCII;
            }
        }

    } else if (current_mode == C1_DECIMAL) {
        int bits_left;

        /* Finish Decimal mode and go back to ASCII unless only one codeword remaining */
        if (codewords_remaining(symbol, tp) > 1) {
            db_p = bin_append_posn(63, 6, decimal_binary, db_p); /* Unlatch */
        }

        if (db_p >= 8) {
            db_p = decimal_binary_transfer(decimal_binary, db_p, target, &tp);
        }

        bits_left = (8 - db_p) & 0x07;

        if (bits_left) {

            if ((bits_left == 4) || (bits_left == 6)) {
                db_p = bin_append_posn(15, 4, decimal_binary, db_p);
            }

            if (bits_left == 2 || bits_left == 6) {
                db_p = bin_append_posn(1, 2, decimal_binary, db_p);
            }

            (void) decimal_binary_transfer(decimal_binary, db_p, target, &tp);
        }
        current_mode = C1_ASCII;

    } else if (current_mode == C1_BYTE) {
        /* Update byte field length unless no codewords remaining */
        if (codewords_remaining(symbol, tp) > 0) {
            int byte_count = tp - (byte_start + 1);
            if (byte_count <= 249) {
                target[byte_start] = byte_count;
            } else {
                /* Insert extra byte field byte */
                memmove(target + byte_start + 2, target + byte_start + 1, sizeof(unsigned int) * byte_count);
                target[byte_start] = 249 + (byte_count / 250);
                target[byte_start + 1] = (byte_count % 250);
                tp++;
            }
        }
    }

    /* Re-check length of data */
    if (tp > 1480) {
        /* Data is too large for symbol */
        return 0;
    }

    *p_last_mode = current_mode;

    if (debug_print) {
        printf("Target (%d):", tp);
        for (i = 0; i < tp; i++) {
            printf(" [%d]", (int) target[i]);
        }
        printf("\nLast Mode: %d\n", *p_last_mode);
    }

    return tp;
}

/* Set symbol from datagrid */
static void block_copy(struct zint_symbol *symbol, char datagrid[136][120], const int start_row, const int start_col,
            const int height, const int width, const int row_offset, const int col_offset) {
    int i, j;

    for (i = start_row; i < (start_row + height); i++) {
        for (j = start_col; j < (start_col + width); j++) {
            if (datagrid[i][j]) {
                set_module(symbol, i + row_offset, j + col_offset);
            }
        }
    }
}

INTERNAL int code_one(struct zint_symbol *symbol, unsigned char source[], int length) {
    int size = 1, i, j;

    char datagrid[136][120];
    int row, col;
    int sub_version = 0;
    rs_t rs;
    int error_number = 0;

    if ((symbol->option_2 < 0) || (symbol->option_2 > 10)) {
        strcpy(symbol->errtxt, "513: Invalid symbol size");
        return ZINT_ERROR_INVALID_OPTION;
    }

    if (symbol->option_2 == 9) {
        /* Version S */
        int codewords;
        large_int elreg;
        unsigned int data[30], ecc[15];
        int block_width;

        if (length > 18) {
            strcpy(symbol->errtxt, "514: Input data too long for Version S");
            return ZINT_ERROR_TOO_LONG;
        }
        if (is_sane(NEON, source, length) == ZINT_ERROR_INVALID_DATA) {
            strcpy(symbol->errtxt, "515: Invalid input data (Version S encodes numeric input only)");
            return ZINT_ERROR_INVALID_DATA;
        }

        size = 9;
        if (length <= 6) {
            /* Version S-10 */
            sub_version = 1;
            codewords = 4;
            block_width = 2;
        } else if (length <= 12) {
            /* Version S-20 */
            sub_version = 2;
            codewords = 8;
            block_width = 4;
        } else {
            /* Version S-30 */
            sub_version = 3;
            codewords = 12;
            block_width = 6;
        }

        if (symbol->debug & ZINT_DEBUG_PRINT) {
            printf("Subversion: %d\n", sub_version);
        }

        /* Convert value plus one to binary */
        large_load_str_u64(&elreg, source, length);
        large_add_u64(&elreg, 1);
        large_uint_array(&elreg, data, codewords, 5 /*bits*/);

        rs_init_gf(&rs, 0x25);
        rs_init_code(&rs, codewords, 0);
        rs_encode_uint(&rs, codewords, data, ecc);

        for (i = 0; i < codewords; i++) {
            data[i + codewords] = ecc[codewords - i - 1];
        }

        if (symbol->debug & ZINT_DEBUG_PRINT) {
            printf("Codewords (%d): ", codewords);
            for (i = 0; i < codewords * 2; i++) printf(" %d", (int) data[i]);
            printf("\n");
        }

        i = 0;
        for (row = 0; row < 2; row++) {
            for (col = 0; col < block_width; col++) {
                datagrid[row * 2][col * 5] = data[i] & 0x10;
                datagrid[row * 2][(col * 5) + 1] = data[i] & 0x08;
                datagrid[row * 2][(col * 5) + 2] = data[i] & 0x04;
                datagrid[(row * 2) + 1][col * 5] = data[i] & 0x02;
                datagrid[(row * 2) + 1][(col * 5) + 1] = data[i] & 0x01;
                datagrid[row * 2][(col * 5) + 3] = data[i + 1] & 0x10;
                datagrid[row * 2][(col * 5) + 4] = data[i + 1] & 0x08;
                datagrid[(row * 2) + 1][(col * 5) + 2] = data[i + 1] & 0x04;
                datagrid[(row * 2) + 1][(col * 5) + 3] = data[i + 1] & 0x02;
                datagrid[(row * 2) + 1][(col * 5) + 4] = data[i + 1] & 0x01;
                i += 2;
            }
        }

        symbol->rows = 8;
        symbol->width = 10 * sub_version + 1;

    } else if (symbol->option_2 == 10) {
        /* Version T */
        unsigned int data[90 + 2]; /* Allow for 90 BYTE mode (+ latch and byte count) */
        unsigned int ecc[22];
        int data_length;
        int data_cw, ecc_cw, block_width;
        int last_mode;

        if (length > 90 || (symbol->eci && length + 7 + chr_cnt(source, length, '\\') > 90)) {
            strcpy(symbol->errtxt, "519: Input data too long for Version T");
            return ZINT_ERROR_TOO_LONG;
        }

        data_length = c1_encode(symbol, source, data, length, &last_mode);

        if (data_length == 0 || data_length > 38) {
            strcpy(symbol->errtxt, "516: Input data too long for Version T");
            return ZINT_ERROR_TOO_LONG;
        }

        size = 10;
        if (data_length <= 10) {
            sub_version = 1;
            data_cw = 10;
            ecc_cw = 10;
            block_width = 4;
        } else if (data_length <= 24) {
            sub_version = 2;
            data_cw = 24;
            ecc_cw = 16;
            block_width = 8;
        } else {
            sub_version = 3;
            data_cw = 38;
            ecc_cw = 22;
            block_width = 12;
        }

        if (symbol->debug & ZINT_DEBUG_PRINT) {
            printf("Padding: %d, Subversion: %d\n", data_cw - data_length, sub_version);
        }

        /* If require padding */
        if (data_cw > data_length) {
            /* If did not finish in ASCII or BYTE mode, switch to ASCII */
            if (last_mode != C1_ASCII && last_mode != C1_BYTE) {
                data[data_length++] = 255; /* Unlatch */
            }
            for (i = data_length; i < data_cw; i++) {
                data[i] = 129; /* Pad */
            }
        }

        /* Calculate error correction data */
        rs_init_gf(&rs, 0x12d);
        rs_init_code(&rs, ecc_cw, 0);
        rs_encode_uint(&rs, data_cw, data, ecc);

        for (i = 0; i < ecc_cw; i++) {
            data[data_cw + i] = ecc[ecc_cw - i - 1];
        }

        if (symbol->debug & ZINT_DEBUG_PRINT) {
            printf("Codewords (%d):", data_cw + ecc_cw);
            for (i = 0; i < data_cw + ecc_cw; i++) printf(" %d", (int) data[i]);
            printf("\n");
        }

        i = 0;
        for (row = 0; row < 5; row++) {
            for (col = 0; col < block_width; col++) {
                datagrid[row * 2][col * 4] = data[i] & 0x80;
                datagrid[row * 2][(col * 4) + 1] = data[i] & 0x40;
                datagrid[row * 2][(col * 4) + 2] = data[i] & 0x20;
                datagrid[row * 2][(col * 4) + 3] = data[i] & 0x10;
                datagrid[(row * 2) + 1][col * 4] = data[i] & 0x08;
                datagrid[(row * 2) + 1][(col * 4) + 1] = data[i] & 0x04;
                datagrid[(row * 2) + 1][(col * 4) + 2] = data[i] & 0x02;
                datagrid[(row * 2) + 1][(col * 4) + 3] = data[i] & 0x01;
                i++;
            }
        }

        symbol->rows = 16;
        symbol->width = (sub_version * 16) + 1;

    } else {
        /* Versions A to H */
        unsigned int data[1480 + 560];
        unsigned int sub_data[185], sub_ecc[70];
        int data_length;
        int data_cw;
        int blocks, data_blocks, ecc_blocks, ecc_length;
        int last_mode;

        data_length = c1_encode(symbol, source, data, length, &last_mode);

        if (data_length == 0) {
            strcpy(symbol->errtxt, "517: Input data is too long");
            return ZINT_ERROR_TOO_LONG;
        }

        for (i = 7; i >= 0; i--) {
            if (c1_data_length[i] >= data_length) {
                size = i + 1;
            }
        }

        if (symbol->option_2 > size) {
            size = symbol->option_2;
        }

        if ((symbol->option_2 != 0) && (symbol->option_2 < size)) {
            strcpy(symbol->errtxt, "518: Input too long for selected symbol size");
            return ZINT_ERROR_TOO_LONG;
        }
        data_cw = c1_data_length[size - 1];

        /* If require padding */
        if (data_cw > data_length) {
            /* If did not finish in ASCII or BYTE mode, switch to ASCII */
            if (last_mode != C1_ASCII && last_mode != C1_BYTE) {
                data[data_length++] = 255; /* Unlatch */
            }
            if (symbol->debug & ZINT_DEBUG_PRINT) {
                printf("Padding: %d\n", data_cw - data_length);
            }
            for (i = data_length; i < data_cw; i++) {
                data[i] = 129; /* Pad */
            }
        } else if (symbol->debug & ZINT_DEBUG_PRINT) {
            printf("No padding\n");
        }

        /* Calculate error correction data */
        blocks = c1_blocks[size - 1];
        data_blocks = c1_data_blocks[size - 1];
        ecc_blocks = c1_ecc_blocks[size - 1];
        ecc_length = c1_ecc_length[size - 1];

        rs_init_gf(&rs, 0x12d);
        rs_init_code(&rs, ecc_blocks, 0);
        for (i = 0; i < blocks; i++) {
            for (j = 0; j < data_blocks; j++) {
                sub_data[j] = data[j * blocks + i];
            }
            rs_encode_uint(&rs, data_blocks, sub_data, sub_ecc);
            for (j = 0; j < ecc_blocks; j++) {
                data[data_cw + j * blocks + i] = sub_ecc[ecc_blocks - 1 - j];
            }
        }

        if (symbol->debug & ZINT_DEBUG_PRINT) {
            printf("Codewords (%d):", data_cw + ecc_length);
            for (i = 0; i < data_cw + ecc_length; i++) printf(" %d", (int) data[i]);
            printf("\n");
        }

        i = 0;
        for (row = 0; row < c1_grid_height[size - 1]; row++) {
            for (col = 0; col < c1_grid_width[size - 1]; col++) {
                datagrid[row * 2][col * 4] = data[i] & 0x80;
                datagrid[row * 2][(col * 4) + 1] = data[i] & 0x40;
                datagrid[row * 2][(col * 4) + 2] = data[i] & 0x20;
                datagrid[row * 2][(col * 4) + 3] = data[i] & 0x10;
                datagrid[(row * 2) + 1][col * 4] = data[i] & 0x08;
                datagrid[(row * 2) + 1][(col * 4) + 1] = data[i] & 0x04;
                datagrid[(row * 2) + 1][(col * 4) + 2] = data[i] & 0x02;
                datagrid[(row * 2) + 1][(col * 4) + 3] = data[i] & 0x01;
                i++;
            }
        }

        symbol->rows = c1_height[size - 1];
        symbol->width = c1_width[size - 1];
    }

    if (symbol->debug & ZINT_DEBUG_PRINT) {
        printf("Version: %d\n", size);
    }

    switch (size) {
        case 1: /* Version A */
            central_finder(symbol, 6, 3, 1);
            vert(symbol, 4, 6, 1);
            vert(symbol, 12, 5, 0);
            set_module(symbol, 5, 12);
            spigot(symbol, 0);
            spigot(symbol, 15);
            block_copy(symbol, datagrid, 0, 0, 5, 4, 0, 0);
            block_copy(symbol, datagrid, 0, 4, 5, 12, 0, 2);
            block_copy(symbol, datagrid, 5, 0, 5, 12, 6, 0);
            block_copy(symbol, datagrid, 5, 12, 5, 4, 6, 2);
            break;
        case 2: /* Version B */
            central_finder(symbol, 8, 4, 1);
            vert(symbol, 4, 8, 1);
            vert(symbol, 16, 7, 0);
            set_module(symbol, 7, 16);
            spigot(symbol, 0);
            spigot(symbol, 21);
            block_copy(symbol, datagrid, 0, 0, 7, 4, 0, 0);
            block_copy(symbol, datagrid, 0, 4, 7, 16, 0, 2);
            block_copy(symbol, datagrid, 7, 0, 7, 16, 8, 0);
            block_copy(symbol, datagrid, 7, 16, 7, 4, 8, 2);
            break;
        case 3: /* Version C */
            central_finder(symbol, 11, 4, 2);
            vert(symbol, 4, 11, 1);
            vert(symbol, 26, 13, 1);
            vert(symbol, 4, 10, 0);
            vert(symbol, 26, 10, 0);
            spigot(symbol, 0);
            spigot(symbol, 27);
            block_copy(symbol, datagrid, 0, 0, 10, 4, 0, 0);
            block_copy(symbol, datagrid, 0, 4, 10, 20, 0, 2);
            block_copy(symbol, datagrid, 0, 24, 10, 4, 0, 4);
            block_copy(symbol, datagrid, 10, 0, 10, 4, 8, 0);
            block_copy(symbol, datagrid, 10, 4, 10, 20, 8, 2);
            block_copy(symbol, datagrid, 10, 24, 10, 4, 8, 4);
            break;
        case 4: /* Version D */
            central_finder(symbol, 16, 5, 1);
            vert(symbol, 4, 16, 1);
            vert(symbol, 20, 16, 1);
            vert(symbol, 36, 16, 1);
            vert(symbol, 4, 15, 0);
            vert(symbol, 20, 15, 0);
            vert(symbol, 36, 15, 0);
            spigot(symbol, 0);
            spigot(symbol, 12);
            spigot(symbol, 27);
            spigot(symbol, 39);
            block_copy(symbol, datagrid, 0, 0, 15, 4, 0, 0);
            block_copy(symbol, datagrid, 0, 4, 15, 14, 0, 2);
            block_copy(symbol, datagrid, 0, 18, 15, 14, 0, 4);
            block_copy(symbol, datagrid, 0, 32, 15, 4, 0, 6);
            block_copy(symbol, datagrid, 15, 0, 15, 4, 10, 0);
            block_copy(symbol, datagrid, 15, 4, 15, 14, 10, 2);
            block_copy(symbol, datagrid, 15, 18, 15, 14, 10, 4);
            block_copy(symbol, datagrid, 15, 32, 15, 4, 10, 6);
            break;
        case 5: /* Version E */
            central_finder(symbol, 22, 5, 2);
            vert(symbol, 4, 22, 1);
            vert(symbol, 26, 24, 1);
            vert(symbol, 48, 22, 1);
            vert(symbol, 4, 21, 0);
            vert(symbol, 26, 21, 0);
            vert(symbol, 48, 21, 0);
            spigot(symbol, 0);
            spigot(symbol, 12);
            spigot(symbol, 39);
            spigot(symbol, 51);
            block_copy(symbol, datagrid, 0, 0, 21, 4, 0, 0);
            block_copy(symbol, datagrid, 0, 4, 21, 20, 0, 2);
            block_copy(symbol, datagrid, 0, 24, 21, 20, 0, 4);
            block_copy(symbol, datagrid, 0, 44, 21, 4, 0, 6);
            block_copy(symbol, datagrid, 21, 0, 21, 4, 10, 0);
            block_copy(symbol, datagrid, 21, 4, 21, 20, 10, 2);
            block_copy(symbol, datagrid, 21, 24, 21, 20, 10, 4);
            block_copy(symbol, datagrid, 21, 44, 21, 4, 10, 6);
            break;
        case 6: /* Version F */
            central_finder(symbol, 31, 5, 3);
            vert(symbol, 4, 31, 1);
            vert(symbol, 26, 35, 1);
            vert(symbol, 48, 31, 1);
            vert(symbol, 70, 35, 1);
            vert(symbol, 4, 30, 0);
            vert(symbol, 26, 30, 0);
            vert(symbol, 48, 30, 0);
            vert(symbol, 70, 30, 0);
            spigot(symbol, 0);
            spigot(symbol, 12);
            spigot(symbol, 24);
            spigot(symbol, 45);
            spigot(symbol, 57);
            spigot(symbol, 69);
            block_copy(symbol, datagrid, 0, 0, 30, 4, 0, 0);
            block_copy(symbol, datagrid, 0, 4, 30, 20, 0, 2);
            block_copy(symbol, datagrid, 0, 24, 30, 20, 0, 4);
            block_copy(symbol, datagrid, 0, 44, 30, 20, 0, 6);
            block_copy(symbol, datagrid, 0, 64, 30, 4, 0, 8);
            block_copy(symbol, datagrid, 30, 0, 30, 4, 10, 0);
            block_copy(symbol, datagrid, 30, 4, 30, 20, 10, 2);
            block_copy(symbol, datagrid, 30, 24, 30, 20, 10, 4);
            block_copy(symbol, datagrid, 30, 44, 30, 20, 10, 6);
            block_copy(symbol, datagrid, 30, 64, 30, 4, 10, 8);
            break;
        case 7: /* Version G */
            central_finder(symbol, 47, 6, 2);
            vert(symbol, 6, 47, 1);
            vert(symbol, 27, 49, 1);
            vert(symbol, 48, 47, 1);
            vert(symbol, 69, 49, 1);
            vert(symbol, 90, 47, 1);
            vert(symbol, 6, 46, 0);
            vert(symbol, 27, 46, 0);
            vert(symbol, 48, 46, 0);
            vert(symbol, 69, 46, 0);
            vert(symbol, 90, 46, 0);
            spigot(symbol, 0);
            spigot(symbol, 12);
            spigot(symbol, 24);
            spigot(symbol, 36);
            spigot(symbol, 67);
            spigot(symbol, 79);
            spigot(symbol, 91);
            spigot(symbol, 103);
            block_copy(symbol, datagrid, 0, 0, 46, 6, 0, 0);
            block_copy(symbol, datagrid, 0, 6, 46, 19, 0, 2);
            block_copy(symbol, datagrid, 0, 25, 46, 19, 0, 4);
            block_copy(symbol, datagrid, 0, 44, 46, 19, 0, 6);
            block_copy(symbol, datagrid, 0, 63, 46, 19, 0, 8);
            block_copy(symbol, datagrid, 0, 82, 46, 6, 0, 10);
            block_copy(symbol, datagrid, 46, 0, 46, 6, 12, 0);
            block_copy(symbol, datagrid, 46, 6, 46, 19, 12, 2);
            block_copy(symbol, datagrid, 46, 25, 46, 19, 12, 4);
            block_copy(symbol, datagrid, 46, 44, 46, 19, 12, 6);
            block_copy(symbol, datagrid, 46, 63, 46, 19, 12, 8);
            block_copy(symbol, datagrid, 46, 82, 46, 6, 12, 10);
            break;
        case 8: /* Version H */
            central_finder(symbol, 69, 6, 3);
            vert(symbol, 6, 69, 1);
            vert(symbol, 26, 73, 1);
            vert(symbol, 46, 69, 1);
            vert(symbol, 66, 73, 1);
            vert(symbol, 86, 69, 1);
            vert(symbol, 106, 73, 1);
            vert(symbol, 126, 69, 1);
            vert(symbol, 6, 68, 0);
            vert(symbol, 26, 68, 0);
            vert(symbol, 46, 68, 0);
            vert(symbol, 66, 68, 0);
            vert(symbol, 86, 68, 0);
            vert(symbol, 106, 68, 0);
            vert(symbol, 126, 68, 0);
            spigot(symbol, 0);
            spigot(symbol, 12);
            spigot(symbol, 24);
            spigot(symbol, 36);
            spigot(symbol, 48);
            spigot(symbol, 60);
            spigot(symbol, 87);
            spigot(symbol, 99);
            spigot(symbol, 111);
            spigot(symbol, 123);
            spigot(symbol, 135);
            spigot(symbol, 147);
            block_copy(symbol, datagrid, 0, 0, 68, 6, 0, 0);
            block_copy(symbol, datagrid, 0, 6, 68, 18, 0, 2);
            block_copy(symbol, datagrid, 0, 24, 68, 18, 0, 4);
            block_copy(symbol, datagrid, 0, 42, 68, 18, 0, 6);
            block_copy(symbol, datagrid, 0, 60, 68, 18, 0, 8);
            block_copy(symbol, datagrid, 0, 78, 68, 18, 0, 10);
            block_copy(symbol, datagrid, 0, 96, 68, 18, 0, 12);
            block_copy(symbol, datagrid, 0, 114, 68, 6, 0, 14);
            block_copy(symbol, datagrid, 68, 0, 68, 6, 12, 0);
            block_copy(symbol, datagrid, 68, 6, 68, 18, 12, 2);
            block_copy(symbol, datagrid, 68, 24, 68, 18, 12, 4);
            block_copy(symbol, datagrid, 68, 42, 68, 18, 12, 6);
            block_copy(symbol, datagrid, 68, 60, 68, 18, 12, 8);
            block_copy(symbol, datagrid, 68, 78, 68, 18, 12, 10);
            block_copy(symbol, datagrid, 68, 96, 68, 18, 12, 12);
            block_copy(symbol, datagrid, 68, 114, 68, 6, 12, 14);
            break;
        case 9: /* Version S */
            horiz(symbol, 5, 1);
            horiz(symbol, 7, 1);
            set_module(symbol, 6, 0);
            set_module(symbol, 6, symbol->width - 1);
            unset_module(symbol, 7, 1);
            unset_module(symbol, 7, symbol->width - 2);
            switch (sub_version) {
                case 1: /* Version S-10 */
                    set_module(symbol, 0, 5);
                    block_copy(symbol, datagrid, 0, 0, 4, 5, 0, 0);
                    block_copy(symbol, datagrid, 0, 5, 4, 5, 0, 1);
                    break;
                case 2: /* Version S-20 */
                    set_module(symbol, 0, 10);
                    set_module(symbol, 4, 10);
                    block_copy(symbol, datagrid, 0, 0, 4, 10, 0, 0);
                    block_copy(symbol, datagrid, 0, 10, 4, 10, 0, 1);
                    break;
                case 3: /* Version S-30 */
                    set_module(symbol, 0, 15);
                    set_module(symbol, 4, 15);
                    set_module(symbol, 6, 15);
                    block_copy(symbol, datagrid, 0, 0, 4, 15, 0, 0);
                    block_copy(symbol, datagrid, 0, 15, 4, 15, 0, 1);
                    break;
            }
            break;
        case 10: /* Version T */
            horiz(symbol, 11, 1);
            horiz(symbol, 13, 1);
            horiz(symbol, 15, 1);
            set_module(symbol, 12, 0);
            set_module(symbol, 12, symbol->width - 1);
            set_module(symbol, 14, 0);
            set_module(symbol, 14, symbol->width - 1);
            unset_module(symbol, 13, 1);
            unset_module(symbol, 13, symbol->width - 2);
            unset_module(symbol, 15, 1);
            unset_module(symbol, 15, symbol->width - 2);
            switch (sub_version) {
                case 1: /* Version T-16 */
                    set_module(symbol, 0, 8);
                    set_module(symbol, 10, 8);
                    block_copy(symbol, datagrid, 0, 0, 10, 8, 0, 0);
                    block_copy(symbol, datagrid, 0, 8, 10, 8, 0, 1);
                    break;
                case 2: /* Version T-32 */
                    set_module(symbol, 0, 16);
                    set_module(symbol, 10, 16);
                    set_module(symbol, 12, 16);
                    block_copy(symbol, datagrid, 0, 0, 10, 16, 0, 0);
                    block_copy(symbol, datagrid, 0, 16, 10, 16, 0, 1);
                    break;
                case 3: /* Verion T-48 */
                    set_module(symbol, 0, 24);
                    set_module(symbol, 10, 24);
                    set_module(symbol, 12, 24);
                    set_module(symbol, 14, 24);
                    block_copy(symbol, datagrid, 0, 0, 10, 24, 0, 0);
                    block_copy(symbol, datagrid, 0, 24, 10, 24, 0, 1);
                    break;
            }
            break;
    }

    for (i = 0; i < symbol->rows; i++) {
        symbol->row_height[i] = 1;
    }
    symbol->height = symbol->rows;

    if (symbol->option_2 == 9) { /* Version S */
        if (symbol->eci || (symbol->input_mode & 0x07) == GS1_MODE) {
            strcpy(symbol->errtxt, "511: ECI and GS1 mode ignored for Version S");
            error_number = ZINT_WARN_INVALID_OPTION;
        }
    } else if (symbol->eci && (symbol->input_mode & 0x07) == GS1_MODE) {
        strcpy(symbol->errtxt, "512: ECI ignored for GS1 mode");
        error_number = ZINT_WARN_INVALID_OPTION;
    }

    return error_number;
}
