/* pdf417.c - Handles PDF417 stacked symbology */
/*
    libzint - the open source barcode library
    Copyright (C) 2008-2026 Robin Stuart <rstuart114@gmail.com>
    Portions Copyright (C) 2004 Grandzebu
    Bug Fixes thanks to KL Chin <klchin@users.sourceforge.net>

    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.
 */
/* SPDX-License-Identifier: BSD-3-Clause */

/*
 *********************************************************************************************
 * TODO: Replace all code adapted from "pdf417.frm" as it was released under GPL v2.0 or later
 *********************************************************************************************
 */

/*  This code is adapted from "Code barre PDF 417 / PDF 417 barcode" v2.5.0
    which is Copyright (C) 2004 (Grandzebu).
    The original code (file pdf417.frm) can be downloaded from https://grandzebu.net/informatique/codbar/pdf417.zip */
/* SPDX-License-Identifier: GPL-2.0-or-later */

/* NOTE: symbol->option_1 is used to specify the security level (i.e. control the
   number of check codewords)

   symbol->option_2 is used to adjust the width of the resulting symbol (i.e. the
   number of codeword columns not including row start and end data)

   symbol->option_3 is used to adjust the rows of the resulting symbol */

#include <assert.h>
#include <limits.h>
#include <math.h>
#include <stdio.h>
#include "common.h"
#include "pdf417.h"
#include "pdf417_tabs.h"

/* Modes */
#define PDF_ALP         1 /* Treating TEX sub-modes as pseudo-modes (minimal encode) */
#define PDF_LOW         2
#define PDF_MIX         3
#define PDF_PNC         4
#define PDF_TEX         4 /* Real modes */
#define PDF_BYT         5
#define PDF_NUM         6

#define PDF_NUM_MODES   6

/* Mode indicators including TEX pseudo-modes */
static const char pdf_smodes[] = { '?', 'A', 'L', 'M', 'P', 'B', 'N' };

/* Return (real) mode text */
static const char *pdf_mode_str(const int mode) {
    static const char modes[3][7] = { "Text", "Byte", "Number" };
    return mode >= PDF_TEX && mode <= PDF_NUM ? modes[mode - PDF_TEX] : "ERROR";
}

#define PDF_REAL_MODE(m) ((m) <= PDF_TEX ? PDF_TEX : (m))

/* TEX mode OR-able sub-modes (tables) */
#define T_ALPHA   1
#define T_LOWER   2
#define T_MIXED   4
#define T_PUNCT   8

#define T_ALWMX   (T_ALPHA | T_LOWER | T_MIXED)
#define T_MXPNC   (T_MIXED | T_PUNCT)

#define PDF_TABLE_TO_MODE(t) (((t) >> 1) + !!((t) & 0x07)) /* Hack to map 1,2,4,8 to 1,2,3,4 */

/*
   Three figure numbers in comments give the location of command equivalents in the
   original Visual Basic source code file pdf417.frm
   this code retains some original (French) procedure and variable names to ease conversion */

/* Text mode processing tables */

/* TEX sub-mode assignments */
static const char pdf_asciix[256] = {
          0,       0,       0,       0,       0,       0,       0,       0, /* 00-07 */
          0, T_MXPNC, T_PUNCT,       0,       0, T_MXPNC,       0,       0, /* 08-0F .<HT><LF>..<CR>.. */
          0,       0,       0,       0,       0,       0,       0,       0, /* 10-17 */
          0,       0,       0,       0,       0,       0,       0,       0, /* 18-1F */
    T_ALWMX, T_PUNCT, T_PUNCT, T_MIXED, T_MXPNC, T_MIXED, T_MIXED, T_PUNCT, /* 20-27 <SP>!"#$%&' */
    T_PUNCT, T_PUNCT, T_MXPNC, T_MIXED, T_MXPNC, T_MXPNC, T_MXPNC, T_MXPNC, /* 28-2F ()*+,-./ */
    T_MIXED, T_MIXED, T_MIXED, T_MIXED, T_MIXED, T_MIXED, T_MIXED, T_MIXED, /* 30-37 01234567 */
    T_MIXED, T_MIXED, T_MXPNC, T_PUNCT, T_PUNCT, T_MIXED, T_PUNCT, T_PUNCT, /* 38-3F 89:;<=>? */
    T_PUNCT, T_ALPHA, T_ALPHA, T_ALPHA, T_ALPHA, T_ALPHA, T_ALPHA, T_ALPHA, /* 40-47 @ABCDEFG */
    T_ALPHA, T_ALPHA, T_ALPHA, T_ALPHA, T_ALPHA, T_ALPHA, T_ALPHA, T_ALPHA, /* 48-4F HIJKLMNO */
    T_ALPHA, T_ALPHA, T_ALPHA, T_ALPHA, T_ALPHA, T_ALPHA, T_ALPHA, T_ALPHA, /* 50-57 PQRSTUVW */
    T_ALPHA, T_ALPHA, T_ALPHA, T_PUNCT, T_PUNCT, T_PUNCT, T_MIXED, T_PUNCT, /* 58-5F XYZ[\]^_ */
    T_PUNCT, T_LOWER, T_LOWER, T_LOWER, T_LOWER, T_LOWER, T_LOWER, T_LOWER, /* 60-67 `abcdefg */
    T_LOWER, T_LOWER, T_LOWER, T_LOWER, T_LOWER, T_LOWER, T_LOWER, T_LOWER, /* 68-6F hijklmno */
    T_LOWER, T_LOWER, T_LOWER, T_LOWER, T_LOWER, T_LOWER, T_LOWER, T_LOWER, /* 70-77 pqrstuvw */
    T_LOWER, T_LOWER, T_LOWER, T_PUNCT, T_PUNCT, T_PUNCT, T_PUNCT,       0, /* 78-7E xyz{|}~D */
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /*80-9F*/
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /*A0-BF*/
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /*C0-DF*/
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /*E0-FF*/
};

/* TEX sub-mode values */
static const char pdf_asciiy[127] = {
     0,  0,  0,  0,  0,  0,  0,  0, /* 00-07 */
     0, 12, 15,  0,  0, 11,  0,  0, /* 08-0F */
     0,  0,  0,  0,  0,  0,  0,  0, /* 10-17 */
     0,  0,  0,  0,  0,  0,  0,  0, /* 18-1F */
    26, 10, 20, 15, 18, 21, 10, 28, /* 20-27 */
    23, 24, 22, 20, 13, 16, 17, 19, /* 28-2F */
     0,  1,  2,  3,  4,  5,  6,  7, /* 30-37 */
     8,  9, 14,  0,  1, 23,  2, 25, /* 38-3F */
     3,  0,  1,  2,  3,  4,  5,  6, /* 40-47 */
     7,  8,  9, 10, 11, 12, 13, 14, /* 48-4F */
    15, 16, 17, 18, 19, 20, 21, 22, /* 50-57 */
    23, 24, 25,  4,  5,  6, 24,  7, /* 58-5F */
     8,  0,  1,  2,  3,  4,  5,  6, /* 60-67 */
     7,  8,  9, 10, 11, 12, 13, 14, /* 68-6F */
    15, 16, 17, 18, 19, 20, 21, 22, /* 70-77 */
    23, 24, 25, 26, 21, 27,  9      /* 78-7F */
};

/* Automatic sizing table */
/* Number of non-EC CWs from ISO/IEC 24728:2006 Table 1, arranged in ascending order (1st 28),
   and matching variant (2nd 28) */
static const char pdf_MicroAutosize[56] = {
    4,  6, 7, 8, 10, 12, 13, 14, 16, 18, 19, 20, 24, 29, 30, 33, 34, 37, 39, 46, 54, 58, 70, 72, 82, 90, 108, 126,
    1, 14, 2, 7,  3, 25,  8, 16,  5, 17,  9,  6, 10, 11, 28, 12, 19, 13, 29, 20, 30, 21, 22, 31, 23, 32,  33,  34
};

/* ISO/IEC 15438:2015 5.1.1 c) 3) Max possible number of characters at error correction level 0
   (Numeric Compaction mode) */
#define PDF_MAX_LEN         2710
#define PDF_MAX_LEN_S       "2710" /* String version of above */
#define PDF_MAX_STREAM_LEN  (PDF_MAX_LEN * 3) /* Allow for tripling up due to shifts/latches (ticket #300 (#7)) */

/* ISO/IEC 24728:2006 5.1.1 c) 3) Max possible number of characters (Numeric Compaction mode) */
#define MICRO_PDF_MAX_LEN   366
#define MICRO_PDF_MAX_LEN_S "366" /* String version of above */

/* 866 */
/* Initial non-compressed categorization of input */
static int pdf_quelmode(const unsigned char codeascii) {
    if (z_isdigit(codeascii)) {
        return PDF_NUM;
    }
    if (pdf_asciix[codeascii]) {
        return PDF_TEX;
    }
    /* 876 */

    return PDF_BYT;
}

/* Helper to switch TEX mode sub-mode */
static int pdf_textprocess_switch(const int curtable, const int newtable, unsigned char chainet[PDF_MAX_STREAM_LEN],
            int wnet) {
    switch (curtable) {
        case T_ALPHA:
            switch (newtable) {
                case T_LOWER:
                    chainet[wnet++] = 27; /* LL */
                    break;
                case T_MIXED:
                    chainet[wnet++] = 28; /* ML */
                    break;
                case T_PUNCT:
                    chainet[wnet++] = 28; /* ML+PL */
                    chainet[wnet++] = 25;
                    break;
            }
            break;
        case T_LOWER:
            switch (newtable) {
                case T_ALPHA:
                    chainet[wnet++] = 28; /* ML+AL */
                    chainet[wnet++] = 28;
                    break;
                case T_MIXED:
                    chainet[wnet++] = 28; /* ML */
                    break;
                case T_PUNCT:
                    chainet[wnet++] = 28; /* ML+PL */
                    chainet[wnet++] = 25;
                    break;
            }
            break;
        case T_MIXED:
            switch (newtable) {
                case T_ALPHA:
                    chainet[wnet++] = 28; /* AL */
                    break;
                case T_LOWER:
                    chainet[wnet++] = 27; /* LL */
                    break;
                case T_PUNCT:
                    chainet[wnet++] = 25; /* PL */
                    break;
            }
            break;
        case T_PUNCT:
            switch (newtable) {
                case T_ALPHA:
                    chainet[wnet++] = 29; /* AL */
                    break;
                case T_LOWER:
                    chainet[wnet++] = 29; /* AL+LL */
                    chainet[wnet++] = 27;
                    break;
                case T_MIXED:
                    chainet[wnet++] = 29; /* AL+ML */
                    chainet[wnet++] = 28;
                    break;
            }
            break;
    }

    return wnet;
}

/* Check consecutive segments for text/num and return the length */
static int pdf_text_num_length(short liste[3][PDF_MAX_LEN], const int indexliste, const int start) {
    int i, len = 0;
    for (i = start; i < indexliste; i++) {
        if (liste[1][i] == PDF_BYT)
            break;

        len += liste[0][i];
        if (len >= 5) /* We don't care if it's longer than 5 */
            break;
    }

    return len;
}

/* Calculate length of TEX allowing for sub-mode switches (no-output version of `pdf_textprocess()`) */
static int pdf_text_submode_length(const unsigned char chaine[], const int start, const int length, int *p_curtable) {
    int j, indexlistet, curtable = *p_curtable, listet[PDF_MAX_LEN], wnet = 0;
    unsigned char chainet[PDF_MAX_STREAM_LEN];

    for (indexlistet = 0; indexlistet < length; indexlistet++) {
        assert(pdf_asciix[chaine[start + indexlistet]]); /* Should only be dealing with TEX */
        listet[indexlistet] = pdf_asciix[chaine[start + indexlistet]];
    }

    for (j = 0; j < length; j++) {
        if (listet[j] & curtable) {
            /* The character is in the current table */
            wnet++;
        } else {
            /* Obliged to change table */
            int newtable;
            if (j == length - 1 || !(listet[j] & listet[j + 1])) {
                /* We change only one character - look for temporary switch */
                if ((listet[j] & T_ALPHA) && curtable == T_LOWER) {
                    wnet += 2; /* AS+char */
                    continue;
                }
                if (listet[j] & T_PUNCT) { /* (T_PUNCT and T_ALPHA not both possible) */
                    wnet += 2; /* PS+char */
                    continue;
                }
                /* No temporary switch available */
                newtable = listet[j];
            } else {
                newtable = listet[j] & listet[j + 1];
            }

            /* 599 */

            /* Maintain the first if several tables are possible */
            if (newtable == T_ALWMX) { /* (T_ALPHA | T_LOWER | T_MIXED) */
                newtable = T_ALPHA;
            } else if (newtable == T_MXPNC) { /* (T_MIXED | T_PUNCT) */
                newtable = T_MIXED;
            }

            /* 619 - select the switch */
            wnet = pdf_textprocess_switch(curtable, newtable, chainet, wnet);
            curtable = newtable;
            /* 659 - at last we add the character */
            wnet++;
        }
    }

    *p_curtable = curtable;

    return wnet;
}

/* Whether to stay in numeric mode or not */
static int pdf_num_stay(const unsigned char *chaine, const int indexliste, short liste[3][PDF_MAX_LEN], const int i) {
    int curtable, not_tex, last_len, last_ml, next_len, num_cws, tex_cws;

    if (liste[0][i] >= 13 || (indexliste == 1 && liste[0][i] > 5)) {
        return 1;
    }
    if (liste[0][i] < 11) {
        return 0;
    }

    curtable = T_ALPHA;
    not_tex = i == 0 || liste[1][i - 1] == PDF_BYT;
    last_len = not_tex ? 0 : pdf_text_submode_length(chaine, liste[2][i - 1], liste[0][i - 1], &curtable);
    last_ml = curtable == T_MIXED;

    curtable = T_ALPHA; /* Next len if after NUM, sub-mode will be alpha */
    not_tex = i == indexliste - 1 || liste[1][i + 1] == PDF_BYT;
    next_len = not_tex ? 0 : pdf_text_submode_length(chaine, liste[2][i + 1], liste[0][i + 1], &curtable);
    num_cws = ((last_len + 1) >> 1) + 1 + 4 + (liste[0][i] > 11) + 1 + ((next_len + 1) >> 1);

    curtable = T_MIXED; /* Next len if stay TEX, sub-mode will be mixed */
    next_len = not_tex ? 0 : pdf_text_submode_length(chaine, liste[2][i + 1], liste[0][i + 1], &curtable);
    tex_cws = (last_len + !last_ml + liste[0][i] + next_len + 1) >> 1;

    if (num_cws > tex_cws) {
        return 0;
    }
    return 1;
}

/* Pack segments using the method described in Appendix D of the AIM specification (ISO/IEC 15438:2015 Annex N) */
static void pdf_appendix_d_encode(const unsigned char *chaine, short liste[3][PDF_MAX_LEN], int *p_indexliste) {
    const int indexliste = *p_indexliste;
    int i = 0, next, last = 0, stayintext = 0;

    while (i < indexliste) {

        if (liste[1][i] == PDF_NUM && pdf_num_stay(chaine, indexliste, liste, i)) {
            /* Leave as numeric */
            liste[0][last] = liste[0][i];
            liste[1][last] = PDF_NUM;
            liste[2][last] = liste[2][i];
            stayintext = 0;
            last++;
        } else if ((liste[1][i] == PDF_TEX || liste[1][i] == PDF_NUM)
                    && (stayintext || i == indexliste - 1 || liste[0][i] >= 5
                        || pdf_text_num_length(liste, indexliste, i) >= 5)) {
            /* Set to text and combine additional text/short numeric segments */
            liste[0][last] = liste[0][i];
            liste[1][last] = PDF_TEX;
            liste[2][last] = liste[2][i];
            stayintext = 0;

            next = i + 1;
            while (next < indexliste) {
                if (liste[1][next] == PDF_NUM && pdf_num_stay(chaine, indexliste, liste, next)) {
                    break;
                } else if (liste[1][next] == PDF_BYT) {
                    break;
                }

                liste[0][last] += liste[0][next];
                next++;
            }

            last++;
            i = next;
            continue;
        } else {
            /* Build byte segment, including combining numeric/text segments that aren't long enough on their own */
            liste[0][last] = liste[0][i];
            liste[1][last] = PDF_BYT;
            liste[2][last] = liste[2][i];
            stayintext = 0;

            next = i + 1;
            while (next < indexliste) {
                if (liste[1][next] != PDF_BYT) {
                    /* Check for case of a single byte shift from text mode */
                    if (liste[0][last] == 1 && last > 0 && liste[1][last - 1] == PDF_TEX) {
                        stayintext = 1;
                        break;
                    }

                    if (liste[0][next] >= 5 || pdf_text_num_length(liste, indexliste, next) >= 5) {
                        break;
                    }
                }

                liste[0][last] += liste[0][next];
                next++;
            }

            last++;
            i = next;
            continue;
        }

        i++;
    }

    /* Set the size of the list based on the last consolidated segment */
    *p_indexliste = last;
}

/* Helper to pad TEX mode, allowing for whether last segment or not, writing out `chainet` */
static void pdf_textprocess_end(short *chainemc, int *p_mclength, const int is_last_seg,
            unsigned char chainet[PDF_MAX_STREAM_LEN], int wnet, int *p_curtable, int *p_tex_padded) {
    int i;

    *p_tex_padded = wnet & 1;
    if (*p_tex_padded) {
        if (is_last_seg) {
            chainet[wnet++] = 29; /* PS or AL */
        } else { /* Can't use PS as may carry over to following segment */
            /* This is sub-optimal if curtable T_ALPHA and following seg lower, or if curtable T_MIXED
               and following seg is lower or punct; TODO: need peek-ahead to table of following seg */
            chainet[wnet++] = 28 + (*p_curtable == T_PUNCT); /* ML (T_ALPHA/T_LOWER) or AL (T_MIXED/T_PUNCT) */
            *p_curtable = *p_curtable == T_ALPHA || *p_curtable == T_LOWER ? T_MIXED : T_ALPHA;
        }
    }

    /* Now translate the string chainet into codewords */

    for (i = 0; i < wnet; i += 2) {
        chainemc[(*p_mclength)++] = (30 * chainet[i]) + chainet[i + 1];
    }
}

/* 547 */
/* Text compaction */
static void pdf_textprocess(short *chainemc, int *p_mclength, const unsigned char chaine[], const int start,
            const int length, const int lastmode, const int is_last_seg, int *p_curtable, int *p_tex_padded) {
    const int real_lastmode = PDF_REAL_MODE(lastmode);
    int j, indexlistet;
    int curtable = real_lastmode == PDF_TEX ? *p_curtable : T_ALPHA; /* Set default table upper alpha */
    int listet[2][PDF_MAX_LEN] = {{0}};
    unsigned char chainet[PDF_MAX_STREAM_LEN];
    int wnet = 0;

    /* Add mode indicator if needed */
    if (real_lastmode != PDF_TEX) {
        chainemc[(*p_mclength)++] = 900;
    }

    /* `listet` will contain the table numbers and the value of each characters */
    for (indexlistet = 0; indexlistet < length; indexlistet++) {
        const int codeascii = chaine[start + indexlistet];
        listet[0][indexlistet] = pdf_asciix[codeascii];
        listet[1][indexlistet] = pdf_asciiy[codeascii];
    }

    /* 570 */

    for (j = 0; j < length; j++) {
        if (listet[0][j] & curtable) {
            /* The character is in the current table */
            chainet[wnet++] = listet[1][j];
        } else {
            /* Obliged to change table */
            int newtable;
            if (j == length - 1 || !(listet[0][j] & listet[0][j + 1])) {
                /* We change only one character - look for temporary switch */
                if ((listet[0][j] & T_ALPHA) && curtable == T_LOWER) {
                    chainet[wnet++] = 27; /* AS */
                    chainet[wnet++] = listet[1][j];
                    continue;
                }
                if (listet[0][j] & T_PUNCT) { /* (T_PUNCT and T_ALPHA not both possible) */
                    chainet[wnet++] = 29; /* PS */
                    chainet[wnet++] = listet[1][j];
                    continue;
                }
                /* No temporary switch available */
                newtable = listet[0][j];
            } else {
                newtable = listet[0][j] & listet[0][j + 1];
            }

            /* 599 */

            /* Maintain the first if several tables are possible */
            if (newtable == T_ALWMX) { /* (T_ALPHA | T_LOWER | T_MIXED) */
                newtable = T_ALPHA;
            } else if (newtable == T_MXPNC) { /* (T_MIXED | T_PUNCT) */
                newtable = T_MIXED;
            }

            /* 619 - select the switch */
            wnet = pdf_textprocess_switch(curtable, newtable, chainet, wnet);
            curtable = newtable;
            /* 659 - at last we add the character */
            chainet[wnet++] = listet[1][j];
        }
    }

    *p_curtable = curtable;
    pdf_textprocess_end(chainemc, p_mclength, is_last_seg, chainet, wnet, p_curtable, p_tex_padded);
}

/* Minimal text compaction */
static void pdf_textprocess_minimal(short *chainemc, int *p_mclength, const unsigned char chaine[],
            short liste[3][PDF_MAX_LEN], const int indexliste, const int lastmode, const int is_last_seg,
            int *p_curtable, int *p_tex_padded, int *p_i) {
    const int real_lastmode = PDF_REAL_MODE(lastmode);
    int i, j, k;
    int curtable = real_lastmode == PDF_TEX ? *p_curtable : T_ALPHA; /* Set default table upper alpha */
    unsigned char chainet[PDF_MAX_STREAM_LEN];
    int wnet = 0;

    /* Add mode indicator if needed */
    if (real_lastmode != PDF_TEX) {
        chainemc[(*p_mclength)++] = 900;
    }

    for (i = *p_i; i < indexliste && PDF_REAL_MODE(liste[1][i]) == PDF_TEX; i++) {
        static const unsigned char newtables[5] = { 0, T_ALPHA, T_LOWER, T_MIXED, T_PUNCT };
        const int newtable = newtables[liste[1][i]];
        const int from = liste[2][i];
        for (j = 0; j < liste[0][i]; j++) {
            const int c = chaine[from + j];
            const int t_table = pdf_asciix[c];
            if (!t_table) { /* BYT Shift? */
                if (wnet & 1) {
                    chainet[wnet++] = 29; /* PS or AL (T_PUNCT) */
                    if (curtable == T_PUNCT) {
                        curtable = T_ALPHA;
                    }
                }
                for (k = 0; k < wnet; k += 2) {
                    chainemc[(*p_mclength)++] = (30 * chainet[k]) + chainet[k + 1];
                }
                chainemc[(*p_mclength)++] = 913; /* BYT Shift */
                chainemc[(*p_mclength)++] = c;
                wnet = 0;
                continue;
            }

            if (newtable != curtable) {
                wnet = pdf_textprocess_switch(curtable, newtable, chainet, wnet);
                curtable = newtable;
            }
            if (curtable == T_LOWER && t_table == T_ALPHA) {
                chainet[wnet++] = 27; /* AS */
            } else if (curtable != T_PUNCT && (t_table & T_PUNCT) && (curtable != T_MIXED || !(t_table & T_MIXED))) {
                chainet[wnet++] = 29; /* PS */
            }
            /* At last we add the character */
            chainet[wnet++] = pdf_asciiy[c];
        }
    }
    *p_i = i ? i - 1 : 0;

    *p_curtable = curtable;
    pdf_textprocess_end(chainemc, p_mclength, is_last_seg, chainet, wnet, p_curtable, p_tex_padded);
}

/* 671 */
/* Byte compaction */
INTERNAL void zint_pdf_byteprocess(short *chainemc, int *p_mclength, const unsigned char chaine[], int start,
                const int length, const int lastmode) {
    const int real_lastmode = PDF_REAL_MODE(lastmode);

    if (length == 1) {
        /* Shift or latch depending on previous mode */
        chainemc[(*p_mclength)++] = real_lastmode == PDF_TEX ? 913 : 901;
        chainemc[(*p_mclength)++] = chaine[start];
    } else {
        int len;
        /* Select the switch for multiple of 6 bytes */
        if (length % 6 == 0) {
            chainemc[(*p_mclength)++] = 924;
        } else {
            /* Default mode for MICROPDF417 is Byte Compaction (ISO/IEC 24728:2006 5.4.3), but not emitting it
             * depends on whether an ECI has been emitted previously (or not) it appears, so simpler and safer
             * to always emit it. */
            chainemc[(*p_mclength)++] = 901;
        }

        len = 0;

        while (len < length) {
            uint64_t total;
            unsigned int chunkLen = length - len;
            if (6 <= chunkLen) { /* Take groups of 6 */
                chunkLen = 6;
                len += chunkLen;
                total = 0;

                while (chunkLen--) {
                    const uint64_t mantisa = chaine[start++];
                    total |= mantisa << (chunkLen * 8);
                }

                chunkLen = 5;

                while (chunkLen--) {
                    chainemc[*p_mclength + chunkLen] = (int) (total % 900);
                    total /= 900;
                }
                *p_mclength += 5;
            } else { /* If there remains a group of less than 6 bytes */
                len += chunkLen;
                while (chunkLen--) {
                    chainemc[(*p_mclength)++] = chaine[start++];
                }
            }
        }
    }
}

/* 712 */
/* Numeric compaction */
static void pdf_numbprocess(short *chainemc, int *p_mclength, const unsigned char chaine[], const int start,
            const int length) {
    int j;

    chainemc[(*p_mclength)++] = 902;

    j = 0;
    while (j < length) {
        int dumlength = 0;
        int p, len, loop, nombre, dummy[50];
        char chainemod[45];
        int longueur = length - j;
        if (longueur > 44) {
            longueur = 44;
        }
        len = longueur + 1;
        chainemod[0] = 1;
        for (loop = 1; loop < len; loop++) {
            chainemod[loop] = z_ctoi(chaine[start + loop + j - 1]);
        }
        do {
            /* 877 - gosub Modulo */
            p = 0;
            nombre = 0;
            for (loop = 0; loop < len; loop++) {
                nombre *= 10;
                nombre += chainemod[loop];
                if (nombre < 900) {
                    if (p) {
                        chainemod[p++] = 0;
                    }
                } else {
                    chainemod[p] = (nombre / 900);
                    nombre -= chainemod[p++] * 900; /* nombre % 900 */
                }
            }
            /* Return to 723 */

            dummy[dumlength++] = nombre;
            len = p;
        } while (p);
        for (loop = dumlength - 1; loop >= 0; loop--) {
            chainemc[(*p_mclength)++] = dummy[loop];
        }
        j += longueur;
    }
}

#ifdef ZINT_TEST /* Wrapper for direct testing */
INTERNAL void zint_test_pdf_numbprocess(short *chainemc, int *p_mclength, const unsigned char chaine[],
                const int start, const int length) {
    pdf_numbprocess(chainemc, p_mclength, chaine, start, length);
}
#endif

/* Minimal encoding */

/* Return number of consecutive chars from `position` in table `t_table` */
static int pdf_table_length(const unsigned char source[], const int length, const int position, const int t_table) {
    int i;

    for (i = position; i < length && (pdf_asciix[source[i]] & t_table); i++);

    return i - position;
}

struct pdf_edge {
    unsigned char mode;
    unsigned short from; /* Position in input data, 0-based */
    unsigned short len;
    unsigned short units; /* Cumulative TEX/NUM/BYT units since entering TEX/NUM/BYT mode */
    unsigned short unit_size; /* Number of codewords based on units since entering TEX/NUM/BYT mode */
    unsigned short size; /* Cumulative number of codewords in previous TEX/NUM/BYT modes */
    unsigned short previous; /* Index into edges array */
};

/* Note 1st row of edges not used so valid previous cannot point there, i.e. won't be zero */
#define PDF_PREVIOUS(edges, edge) \
    ((edge)->previous ? (edges) + (edge)->previous : NULL)

#if 0
#include "pdf417_trace.h"
#else
#define PDF_TRACE_Edges(px, s, l, p, v)
#define PDF_TRACE_AddEdge(s, l, es, p, v, t, e) do { (void)(s); (void)(l); } while (0)
#define PDF_TRACE_NotAddEdge(s, l, es, p, v, t, e) do { (void)(s); (void)(l); } while (0)
#endif

/* Initialize a new edge */
static int pdf_new_Edge(struct pdf_edge *edges, const int mode, const int from, const int len, const int t_table,
            const int lastmode, struct pdf_edge *previous, struct pdf_edge *edge) {
    const int real_mode = PDF_REAL_MODE(mode);
    int previousMode, real_previousMode;
    int units;
    int unit_size = 0; /* Suppress clang-tidy clang-analyzer-core.uninitialized.Assign warning */
    int dv, md;

    edge->mode = mode;
    edge->from = from;
    edge->len = len;
    if (previous) {
        assert(previous->mode && previous->len && (previous->unit_size + previous->size));
        previousMode = previous->mode;
        real_previousMode = PDF_REAL_MODE(previousMode);
        edge->previous = previous - edges;
        if (real_mode != real_previousMode) {
            edge->size = previous->size + previous->unit_size + 1; /* + TEX/NUM/BYT switch */
            units = 0;
        } else {
            edge->size = previous->size;
            units = previous->units;
        }
    } else {
        previousMode = lastmode;
        real_previousMode = PDF_REAL_MODE(previousMode);
        edge->previous = 0;
        edge->size = real_mode != real_previousMode || real_previousMode != PDF_TEX ? 1 : 0;
        units = 0;
    }

    switch (mode) {
        case PDF_ALP:
            assert(!t_table || (t_table & (T_ALPHA | T_PUNCT)));
            if (t_table) {
                if (previousMode != mode && real_previousMode == PDF_TEX) {
                    units += 1 + (previousMode == PDF_LOW); /* AL or ML+AL */
                }
                units += (1 + !(t_table & T_ALPHA)) * len; /* chars or PS + char */
            } else { /* Binary shift */
                assert(len == 1);
                if (units & 1) {
                    units++; /* PS or AL pad */
                }
                if (previousMode != mode && real_previousMode == PDF_TEX) {
                    units += 1 + (previousMode == PDF_LOW); /* AL or ML+AL */
                }
                units += 4; /* BYT SHIFT 913 (2 units) + byte (2 units) */
            }
            unit_size = (units + 1) >> 1;
            break;

        case PDF_LOW:
            assert(!t_table || (t_table & (T_LOWER | T_PUNCT | T_ALPHA)));
            if (t_table) {
                if (previousMode != mode) {
                    units += 1 + (previousMode == PDF_PNC); /* LL or AL+LL */
                }
                units += (1 + !(t_table & T_LOWER)) * len; /* chars or PS/AS + char */
            } else { /* Binary shift */
                assert(len == 1);
                if (units & 1) {
                    units++; /* PS or AL pad */
                }
                if (previousMode != mode) {
                    units += 1 + (previousMode == PDF_PNC); /* LL or AL+LL */
                }
                units += 4; /* BYT SHIFT 913 (2 units) + byte (2 units) */
            }
            unit_size = (units + 1) >> 1;
            break;

        case PDF_MIX:
            assert(!t_table || (t_table & (T_MIXED | T_PUNCT)));
            if (t_table) {
                if (previousMode != mode) {
                    units += 1 + (previousMode == PDF_PNC); /* ML or AL+ML */
                }
                units += (1 + !(t_table & T_MIXED)) * len; /* chars or PS + char */
            } else { /* Binary shift */
                assert(len == 1);
                if (units & 1) {
                    units++; /* PS pad */
                }
                if (previousMode != mode) {
                    units += 1 + (previousMode == PDF_PNC); /* ML or AL+ML */
                }
                units += 4; /* BYT SHIFT 913 (2 units) + byte (2 units) */
            }
            unit_size = (units + 1) >> 1;
            break;

        case PDF_PNC:
            assert(!t_table || (t_table & T_PUNCT));
            if (t_table) {
                if (previousMode != mode) {
                    units += 1 + (previousMode != PDF_MIX); /* PL or ML+PL */
                }
                units += len; /* chars */
            } else { /* Binary shift */
                assert(len == 1);
                if (units & 1) {
                    units += 3; /* AL pad + (after BYT SHIFT) ML+PL to return to PNC */
                } else if (previousMode != mode) {
                    units += 1 + (previousMode != PDF_MIX); /* PL or ML+PL */
                }
                units += 4; /* BYT SHIFT 913 (2 units) + byte (2 units) */
            }
            unit_size = (units + 1) >> 1;
            break;

        case PDF_BYT:
            units += len;
            dv = units / 6;
            unit_size = dv * 5 + (units - dv * 6);
            break;

        case PDF_NUM:
            units += len;
            dv = units / 44;
            md = units - dv * 44;
            unit_size = dv * 15 + (md ? md / 3 + 1 : 0);
            break;
    }
    edge->units = units;
    edge->unit_size = unit_size;

    return edge->size + edge->unit_size;
}

/* Whether `new_units` likely to result in less (fractional) codewords than `existing_units`, allowing for `mode` */
static int pdf_new_units_better(const int mode, const int existing_units, const int new_units) {
    if (PDF_REAL_MODE(mode) == PDF_TEX) {
        if ((new_units & 1) != (existing_units & 1)) {
            return (existing_units & 1);
        }
    } else if (mode == PDF_BYT) {
        const int existing_md = existing_units % 6;
        if (new_units % 6 != existing_md) {
            return existing_md;
        }
    }
    return new_units < existing_units;
}

/* Add an edge for a mode at a vertex if no existing edge or if more optimal than existing edge */
static void pdf_addEdge(const unsigned char *source, const int length, struct pdf_edge *edges, const int mode,
            const int from, const int len, const int t_table, const int lastmode, struct pdf_edge *previous) {
    struct pdf_edge edge;
    const int new_size = pdf_new_Edge(edges, mode, from, len, t_table, lastmode, previous, &edge);
    const int vertexIndex = from + len;
    const int v_ij = vertexIndex * PDF_NUM_MODES + mode - 1;
    const int v_size = edges[v_ij].size + edges[v_ij].unit_size;

    if (edges[v_ij].mode == 0 || v_size > new_size
            || (v_size == new_size && pdf_new_units_better(mode, edge.units, edges[v_ij].units))) {
        PDF_TRACE_AddEdge(source, length, edges, previous, vertexIndex, t_table, &edge);
        edges[v_ij] = edge;
    } else {
        PDF_TRACE_NotAddEdge(source, length, edges, previous, vertexIndex, t_table, &edge);
    }
}

/* Add edges for the various modes at a vertex */
static void pdf_addEdges(const unsigned char source[], const int length, const int lastmode, struct pdf_edge *edges,
            const int from, struct pdf_edge *previous) {
    const unsigned char c = source[from];
    const int t_table = pdf_asciix[c];

    if (t_table & T_ALPHA) {
        const int len = pdf_table_length(source, length, from, T_ALPHA);
        pdf_addEdge(source, length, edges, PDF_ALP, from, len, T_ALPHA, lastmode, previous);
    }
    if (!t_table || (t_table & T_PUNCT)) { /* Binary shift or PS */
        pdf_addEdge(source, length, edges, PDF_ALP, from, 1 /*len*/, t_table & ~T_ALPHA, lastmode, previous);
    }

    if (t_table & T_LOWER) {
        const int len = pdf_table_length(source, length, from, T_LOWER);
        pdf_addEdge(source, length, edges, PDF_LOW, from, len, T_LOWER, lastmode, previous);
    }
    if (!t_table || (t_table & (T_PUNCT | T_ALPHA))) { /* Binary shift or PS/AS */
        pdf_addEdge(source, length, edges, PDF_LOW, from, 1 /*len*/, t_table & ~T_LOWER, lastmode, previous);
    }

    if (t_table & T_MIXED) {
        const int len = pdf_table_length(source, length, from, T_MIXED);
        pdf_addEdge(source, length, edges, PDF_MIX, from, len, T_MIXED, lastmode, previous);
        if (len > 1 && z_isdigit(source[from + 1])) { /* Add single-length edge before digit to compare to NUM */
            pdf_addEdge(source, length, edges, PDF_MIX, from, 1 /*len*/, T_MIXED, lastmode, previous);
        }
    }
    if (!t_table || (t_table & T_PUNCT)) { /* Binary shift or PS */
        pdf_addEdge(source, length, edges, PDF_MIX, from, 1 /*len*/, t_table & ~T_MIXED, lastmode, previous);
    }

    if (t_table & T_PUNCT) {
        const int len = pdf_table_length(source, length, from, T_PUNCT);
        pdf_addEdge(source, length, edges, PDF_PNC, from, len, T_PUNCT, lastmode, previous);
    }
    if (!t_table) { /* Binary shift */
        pdf_addEdge(source, length, edges, PDF_PNC, from, 1 /*len*/, t_table, lastmode, previous);
    }

    if (z_isdigit(c)) {
        const int len = z_cnt_digits(source, length, from, -1 /*all*/);
        pdf_addEdge(source, length, edges, PDF_NUM, from, len, 0 /*t_table*/, lastmode, previous);
    }

    pdf_addEdge(source, length, edges, PDF_BYT, from, 1 /*len*/, 0 /*t_table*/, lastmode, previous);
}

/* Calculate optimized encoding modes */
static int pdf_define_modes(short liste[3][PDF_MAX_LEN], int *p_indexliste, const unsigned char source[],
            const int length, const int lastmode, const int debug_print) {

    int i, j, v_i;
    int minimalJ, minimalSize;
    struct pdf_edge *edge;
    int mode_start, mode_len;

    struct pdf_edge *edges = (struct pdf_edge *) calloc((length + 1) * PDF_NUM_MODES, sizeof(struct pdf_edge));
    if (!edges) {
        return 0;
    }
    assert((length + 1) * PDF_NUM_MODES < USHRT_MAX); /* Guaranteed by input length limit */

    pdf_addEdges(source, length, lastmode, edges, 0, NULL);

    PDF_TRACE_Edges("DEBUG Initial situation\n", source, length, edges, 0);

    for (i = 1; i < length; i++) {
        v_i = i * PDF_NUM_MODES;
        for (j = 0; j < PDF_NUM_MODES; j++) {
            if (edges[v_i + j].mode) {
                pdf_addEdges(source, length, lastmode, edges, i, edges + v_i + j);
            }
        }
        PDF_TRACE_Edges("DEBUG situation after adding edges to vertices at position %d\n", source, length, edges, i);
    }

    PDF_TRACE_Edges("DEBUG Final situation\n", source, length, edges, length);

    v_i = length * PDF_NUM_MODES;
    minimalJ = -1;
    minimalSize = INT_MAX;
    for (j = 0; j < PDF_NUM_MODES; j++) {
        edge = edges + v_i + j;
        if (edge->mode) {
            const int edge_size = edge->size + edge->unit_size;
            if (debug_print) {
                printf("edges[%d][%d][0] size %d(%d,%d)\n", length, j, edge_size, edge->unit_size, edge->size);
            }
            if (edge_size < minimalSize) {
                minimalSize = edge_size;
                minimalJ = j;
                if (debug_print) printf(" set minimalJ %d\n", minimalJ);
            }
        } else {
            if (debug_print) printf("edges[%d][%d][0] NULL\n", length, j);
        }
    }
    assert(minimalJ >= 0);

    edge = edges + v_i + minimalJ;
    mode_len = 0;
    mode_start = length;
    while (edge) {
        const int current_mode = edge->mode;
        const int current_from = edge->from;
        mode_len += edge->len;
        edge = PDF_PREVIOUS(edges, edge);
        if (!edge || edge->mode != current_mode) {
            mode_start--;
            liste[0][mode_start] = mode_len;
            liste[1][mode_start] = current_mode;
            liste[2][mode_start] = current_from;
            mode_len = 0;
        }
    }
    *p_indexliste = length - mode_start;
    if (mode_start) {
        memmove(liste[0], liste[0] + mode_start, sizeof(short) * (*p_indexliste));
        memmove(liste[1], liste[1] + mode_start, sizeof(short) * (*p_indexliste));
        memmove(liste[2], liste[2] + mode_start, sizeof(short) * (*p_indexliste));
    }
    if (debug_print) {
        printf("modes (%d):", *p_indexliste);
        for (i = 0; i < *p_indexliste; i++) printf(" %c(%d,%d)", pdf_smodes[liste[1][i]], liste[2][i], liste[0][i]);
        fputc('\n', stdout);
    }

    free(edges);

    return 1;
}

/* Initial processing of data, shared by `pdf417()` and `micropdf417()` */
static int pdf_initial(struct zint_symbol *symbol, const unsigned char chaine[], const int length, const int eci,
            const int is_micro, const int is_last_seg, int *p_lastmode, int *p_curtable, int *p_tex_padded,
            short chainemc[PDF_MAX_STREAM_LEN], int *p_mclength) {
    int i, indexchaine = 0, indexliste = 0;
    short liste[3][PDF_MAX_LEN] = {{0}};
    int mclength;
    const int fast_encode = symbol->input_mode & FAST_MODE;
    const int debug_print = symbol->debug & ZINT_DEBUG_PRINT;

    /* 456 */

    if (fast_encode) {
        int mode = pdf_quelmode(chaine[0]);

        /* 463 */
        do {
            liste[1][indexliste] = mode;
            liste[2][indexliste] = indexchaine;
            while (liste[1][indexliste] == mode && indexchaine < length) {
                liste[0][indexliste]++;
                indexchaine++;
                mode = pdf_quelmode(chaine[indexchaine]);
            }
            indexliste++;
        } while (indexchaine < length);

        if (debug_print) {
            fputs("\nInitial block pattern:\n", stdout);
            for (i = 0; i < indexliste; i++) {
                int j;
                for (j = 0; j < liste[0][i]; j++) fputc(pdf_mode_str(liste[1][i])[0], stdout);
            }
            fputc('\n', stdout);
        }

        pdf_appendix_d_encode(chaine, liste, &indexliste);
     } else {
        if (!pdf_define_modes(liste, &indexliste, chaine, length, *p_lastmode, debug_print)) {
            return z_errtxt(ZINT_ERROR_MEMORY, symbol, 749, "Insufficient memory for mode buffers");
        }
    }

    if (debug_print) {
        fputs("\nCompacted block pattern:\n", stdout);
        for (i = 0; i < indexliste; i++) {
            int j;
            for (j = 0; j < liste[0][i]; j++) fputc(pdf_mode_str(PDF_REAL_MODE(liste[1][i]))[0], stdout);
        }
        fputc('\n', stdout);
    }

    /* 541 - now compress the data */
    indexchaine = 0;
    mclength = *p_mclength;
    if (mclength == 0 && !is_micro) {
        mclength++; /* Allow for length descriptor for full symbol */
    }

    if (*p_mclength == 0 && (symbol->output_options & READER_INIT)) {
        chainemc[mclength++] = 921; /* Reader Initialisation */
    }

    if (eci != 0) {
        if (eci > 811799) {
            return z_errtxtf(ZINT_ERROR_INVALID_OPTION, symbol, 472, "ECI code '%d' out of range (0 to 811799)",
                            symbol->eci);
        }
        /* Encoding ECI assignment number, according to Table 8 */
        if (eci <= 899) {
            chainemc[mclength++] = 927; /* ECI */
            chainemc[mclength++] = eci;
        } else if (eci <= 810899) {
            chainemc[mclength++] = 926; /* ECI */
            chainemc[mclength++] = (eci / 900) - 1;
            chainemc[mclength++] = eci % 900;
        } else {
            chainemc[mclength++] = 925; /* ECI */
            chainemc[mclength++] = eci - 810900;
        }
    }

    for (i = 0; i < indexliste; i++) {
        const int real_mode = PDF_REAL_MODE(liste[1][i]);
        switch (real_mode) {
            case PDF_TEX: /* 547 - text mode */
                if (fast_encode) {
                    pdf_textprocess(chainemc, &mclength, chaine, indexchaine, liste[0][i], *p_lastmode, is_last_seg,
                                    p_curtable, p_tex_padded);
                    indexchaine += liste[0][i];
                    *p_lastmode = PDF_ALP;
                } else {
                    pdf_textprocess_minimal(chainemc, &mclength, chaine, liste, indexliste, *p_lastmode, is_last_seg,
                                            p_curtable, p_tex_padded, &i);
                    indexchaine = i + 1 < indexliste ? liste[2][i + 1] : length;
                    *p_lastmode = PDF_TABLE_TO_MODE(*p_curtable);
                }
                break;
            case PDF_BYT: /* 670 - octet stream mode */
                zint_pdf_byteprocess(chainemc, &mclength, chaine, indexchaine, liste[0][i], *p_lastmode);
                /* Don't switch mode on single byte shift from text mode */
                if (PDF_REAL_MODE(*p_lastmode) != PDF_TEX || liste[0][i] != 1) {
                    *p_lastmode = PDF_BYT;
                } else if (*p_curtable == T_PUNCT && *p_tex_padded) { /* If T_PUNCT and padded with AL */
                    /* Then need to reset to alpha - ISO/IEC 15438:2015 5.4.2.4 b) 2) */
                    *p_curtable = T_ALPHA;
                }
                indexchaine += liste[0][i];
                break;
            case PDF_NUM: /* 712 - numeric mode */
                pdf_numbprocess(chainemc, &mclength, chaine, indexchaine, liste[0][i]);
                *p_lastmode = PDF_NUM;
                indexchaine += liste[0][i];
                break;
        }
    }

    *p_mclength = mclength;

    return 0;
}

/* Call `pdf_initial()` for each segment, dealing with Structured Append beforehand */
static int pdf_initial_segs(struct zint_symbol *symbol, struct zint_seg segs[], const int seg_count,
            const int is_micro, short chainemc[PDF_MAX_STREAM_LEN], int *p_mclength, int structapp_cws[18],
            int *p_structapp_cp) {
    int i;
    int error_number = 0;
    int structapp_cp = 0;
    int lastmode;
    int curtable;
    int tex_padded;
    /* Raw text dealt with by `ZBarcode_Encode_Segs()`, except for `eci` feedback */
    const int content_segs = symbol->output_options & BARCODE_CONTENT_SEGS;

    *p_mclength = 0;

    if (symbol->structapp.count) {
        int id_cnt = 0, ids[10];

        if (symbol->structapp.count < 2 || symbol->structapp.count > 99999) {
            return z_errtxtf(ZINT_ERROR_INVALID_OPTION, symbol, 740,
                            "Structured Append count '%d' out of range (2 to 99999)", symbol->structapp.count);
        }
        if (symbol->structapp.index < 1 || symbol->structapp.index > symbol->structapp.count) {
            return ZEXT z_errtxtf(ZINT_ERROR_INVALID_OPTION, symbol, 741,
                                    "Structured Append index '%1$d' out of range (1 to count %2$d)",
                                    symbol->structapp.index, symbol->structapp.count);
        }
        if (symbol->structapp.id[0]) {
            int id_len;

            for (id_len = 1; id_len < 31 && symbol->structapp.id[id_len]; id_len++);

            if (id_len > 30) { /* 10 triplets */
                return z_errtxtf(ZINT_ERROR_INVALID_OPTION, symbol, 742,
                                "Structured Append ID length %d too long (30 digit maximum)", id_len);
            }

            for (i = 0; i < id_len; i += 3, id_cnt++) {
                const int triplet_len = i + 3 < id_len ? 3 : id_len - i;
                ids[id_cnt] = z_to_int(ZCUCP(symbol->structapp.id + i), triplet_len);
                if (ids[id_cnt] == -1) {
                    return z_errtxt(ZINT_ERROR_INVALID_OPTION, symbol, 743,
                                    "Invalid Structured Append ID (digits only)");
                }
                if (ids[id_cnt] > 899) {
                    return ZEXT z_errtxtf(ZINT_ERROR_INVALID_OPTION, symbol, 744,
                                        "Structured Append ID triplet %1$d value '%2$03d' out of range (000 to 899)",
                                        id_cnt + 1, ids[id_cnt]);
                }
            }
        }
        structapp_cws[structapp_cp++] = 928; /* Macro marker */
        structapp_cws[structapp_cp++] = (100000 + symbol->structapp.index - 1) / 900; /* Segment index 1 */
        structapp_cws[structapp_cp++] = (100000 + symbol->structapp.index - 1) % 900; /* Segment index 2 */
        for (i = 0; i < id_cnt; i++) {
            structapp_cws[structapp_cp++] = ids[i];
        }
        structapp_cws[structapp_cp++] = 923; /* Optional field */
        structapp_cws[structapp_cp++] = 1; /* Segment count tag */
        structapp_cws[structapp_cp++] = (100000 + symbol->structapp.count) / 900; /* Segment count 1 */
        structapp_cws[structapp_cp++] = (100000 + symbol->structapp.count) % 900; /* Segment count 2 */
        if (symbol->structapp.index == symbol->structapp.count) {
            structapp_cws[structapp_cp++] = 922; /* Special last segment terminator */
        }
    }
    *p_structapp_cp = structapp_cp;

    /* Default mode for PDF417 is Text Compaction Alpha (ISO/IEC 15438:2015 5.4.2.1), and for MICROPDF417 is Byte
     * Compaction (ISO/IEC 24728:2006 5.4.3) */
    lastmode = is_micro ? PDF_BYT : PDF_ALP;
    /* Start in upper alpha - tracked across calls to `pdf_textprocess()` to allow for interleaving byte shifts */
    curtable = T_ALPHA;

    for (i = 0; i < seg_count; i++) {
        if ((error_number = pdf_initial(symbol, segs[i].source, segs[i].length, segs[i].eci, is_micro,
                                    i + 1 == seg_count, &lastmode, &curtable, &tex_padded, chainemc, p_mclength))) {
            assert(error_number >= ZINT_ERROR);
            return error_number;
        }
        if (content_segs && segs[i].eci) {
            z_ct_set_seg_eci(symbol, i, segs[i].eci);
        }
    }

    return error_number;
}

/* 366 */
/* Encode PDF417 */
static int pdf_enc(struct zint_symbol *symbol, struct zint_seg segs[], const int seg_count) {
    int i, j, longueur, loop, mccorrection[520] = {0}, offset;
    int total, mclength, c1, c2, c3, dummy[35];
    short chainemc[PDF_MAX_STREAM_LEN];
    int rows, cols, ecc, ecc_cws, padding;
    char pattern[580];
    int bp = 0;
    int structapp_cws[18] = {0}; /* 3 (Index) + 10 (ID) + 4 (Count) + 1 (Last) */
    int structapp_cp = 0;
    int error_number;
    const int debug_print = symbol->debug & ZINT_DEBUG_PRINT;

    if ((i = z_segs_length(segs, seg_count)) > PDF_MAX_LEN) {
        return z_errtxtf(ZINT_ERROR_TOO_LONG, symbol, 463, "Input length %d too long (maximum " PDF_MAX_LEN_S ")", i);
    }

    if ((error_number = pdf_initial_segs(symbol, segs, seg_count, 0 /*is_micro*/, chainemc, &mclength, structapp_cws,
                                        &structapp_cp))) {
        assert(error_number >= ZINT_ERROR);
        return error_number;
    }

    if (debug_print) {
        printf("\nCompressed data stream (%d):\n", mclength - 1);
        for (i = 1; i < mclength; i++) { /* Skip unset length descriptor */
            printf("%d ", chainemc[i]);
        }
        fputs("\n\n", stdout);
    }
    assert(mclength > 0); /* Suppress clang-tidy-20.1 warning clang-analyzer-core.CallAndMessage */

    /* 752 - Now take care of the number of CWs per row */

    /* ECC */
    ecc = symbol->option_1;
    if (ecc < 0) { /* If not specified, set ECC depending on no. of codewords */
        const int data_cws = mclength - 1 + structapp_cp; /* -1 for length descriptor */
        /* ISO/IEC 15438:2015 Annex E Table E.1 Recommended minima */
        if (data_cws <= 40) {
            ecc = 2;
        } else if (data_cws <= 160) {
            ecc = 3;
        } else if (data_cws <= 320) {
            ecc = 4;
        } else if (data_cws <= 863) {
            ecc = 5;
        } else {
            ecc = 6; /* Not mentioned in Table E.1 */
        }
    }
    ecc_cws = 2 << ecc;

    longueur = mclength + structapp_cp + ecc_cws;

    if (debug_print) printf("Total No. of Codewords: %d, ECC %d, No. of ECC Codewords: %d\n", longueur, ecc, ecc_cws);

    if (longueur > 928) {
        /* Enforce maximum codeword limit */
        return z_errtxt(ZINT_ERROR_TOO_LONG, symbol, 464,
                        "Input too long, requires too many codewords (maximum 928)");
    }

    cols = symbol->option_2;
    rows = symbol->option_3;
    if (rows) { /* Rows given */
        if (cols < 1) { /* Cols automatic */
            cols = (longueur + rows - 1) / rows;
            if (cols <= 1) {
                cols = 1;
            } else {
                /* Increase rows if would need > 30 columns */
                for (; cols > 30 && rows < 90; rows++, cols = (longueur + rows - 1) / rows);
                assert(cols <= 30);
                /* Increase rows if multiple too big */
                for (; cols >= 1 && rows < 90 && rows * cols > 928; rows++, cols = (longueur + rows - 1) / rows);
                if (rows * cols > 928) {
                    return z_errtxt(ZINT_ERROR_TOO_LONG, symbol, 465,
                                    "Input too long, requires too many codewords (maximum 928)");
                }
            }
        } else { /* Cols given */
            /* Increase rows if multiple too big */
            for (; rows <= 90 && rows * cols < longueur; rows++);
            if (rows > 90 || rows * cols > 928) {
                return z_errtxtf(ZINT_ERROR_TOO_LONG, symbol, 745, "Input too long for number of columns '%d'", cols);
            }
        }
        if (rows != symbol->option_3) {
            error_number = ZEXT z_errtxtf(ZINT_WARN_INVALID_OPTION, symbol, 746,
                                            "Number of rows increased from %1$d to %2$d", symbol->option_3, rows);
        }
    } else { /* Rows automatic, cols automatic or given */
        if (cols < 1) { /* Cols automatic */
            cols = (int) round(sqrt((longueur - 1) / 3.0)); /* -1 (length descriptor) for back-compatibility */
        }
        rows = (longueur + cols - 1) / cols;
        if (rows <= 3) {
            rows = 3;
        } else {
            /* Increase cols if would need > 90 rows - do this even if cols specified for better back-compatibility
               (though previously only increased once) */
            for (; rows > 90 && cols < 30; cols++, rows = (longueur + cols - 1) / cols);
            assert(rows <= 90);
            /* Increase cols if multiple too big */
            for (; rows >= 3 && cols < 30 && rows * cols > 928; cols++, rows = (longueur + cols - 1) / cols);
            if (rows * cols > 928) {
                return z_errtxt(ZINT_ERROR_TOO_LONG, symbol, 747,
                                "Input too long, requires too many codewords (maximum 928)");
            }
            if (symbol->option_2 && cols != symbol->option_2) { /* Note previously did not warn if cols auto-upped */
                error_number = ZEXT z_errtxtf(ZINT_WARN_INVALID_OPTION, symbol, 748,
                                            "Number of columns increased from %1$d to %2$d", symbol->option_2, cols);
            }
        }
    }
    assert(rows * cols >= longueur);

    /* Feedback options */
    symbol->option_1 = ecc;
    symbol->option_2 = cols;
    symbol->option_3 = rows; /* Same as `symbol->rows` */

    /* 781 - Padding calculation */
    padding = rows * cols - longueur;

    /* We add the padding */
    for (i = 0; i < padding; i++) {
        chainemc[mclength++] = 900;
    }
    if (debug_print) printf("Padding: %d\n", padding);

    /* We add the Structured Append Macro Control Block if any */
    if (structapp_cp) {
        for (i = 0; i < structapp_cp; i++) {
            chainemc[mclength++] = structapp_cws[i];
        }
    }

    /* Set the length descriptor */
    chainemc[0] = mclength;

    /* 796 - we now take care of the Reed Solomon codes */
    assert(ecc >= 0 && ecc <= 8);
    offset = (2 << ecc) - 2;
    assert(offset >= 0 && offset <= 512); /* Suppress clang-tidy-23 warning clang-analyzer-security.ArrayBound */

    for (i = 0; i < mclength; i++) {
        total = (chainemc[i] + mccorrection[ecc_cws - 1]) % 929;
        for (j = ecc_cws - 1; j > 0; j--) {
            mccorrection[j] = (mccorrection[j - 1] + 929 - (total * zint_pdf_coefrs[offset + j]) % 929) % 929;
        }
        mccorrection[0] = (929 - (total * zint_pdf_coefrs[offset]) % 929) % 929;
    }

    /* We add these codes to the string */
    for (i = ecc_cws - 1; i >= 0; i--) {
        chainemc[mclength++] = mccorrection[i] ? 929 - mccorrection[i] : 0;
    }

    if (debug_print) {
        printf("Complete CW string (%d):\n", mclength);
        for (i = 0; i < mclength; i++) {
            printf("%d ", chainemc[i]);
        }
        fputc('\n', stdout);
    }
#ifdef ZINT_TEST
    if (symbol->debug & ZINT_DEBUG_TEST) {
        z_debug_test_codeword_dump_short(symbol, chainemc, mclength);
    }
#endif

    if (debug_print) printf("\nSymbol size:\n%d columns x %d rows\n", cols, rows);

    assert(cols > 0); /* Suppress clang-tidy-21 clang-analyzer-security.ArrayBound */

    /* 818 - The CW string is finished */
    c1 = (rows - 1) / 3;
    c2 = ecc * 3 + (rows - 1) % 3;
    c3 = cols - 1;

    /* We now encode each row */
    assert(rows * cols <= mclength); /* Suppress clang-tidy-20.1 warning clang-analyzer-core.uninitialized.Assign */
    for (i = 0; i < rows; i++) {
        const int k = (i / 3) * 30;
        bp = 0;
        for (j = 0; j < cols; j++) {
            dummy[j + 1] = chainemc[i * cols + j];
        }
        switch (i % 3) {
            case 0:
                dummy[0] = k + c1;
                dummy[cols + 1] = k + c3;
                offset = 0; /* cluster(0) */
                break;
            case 1:
                dummy[0] = k + c2;
                dummy[cols + 1] = k + c1;
                offset = 929; /* cluster(3) */
                break;
            case 2:
                dummy[0] = k + c3;
                dummy[cols + 1] = k + c2;
                offset = 1858; /* cluster(6) */
                break;
        }
        bp = z_bin_append_posn(0x1FEA8, 17, pattern, bp); /* Row start */

        for (j = 0; j <= cols; j++) {
            bp = z_bin_append_posn(((int) zint_pdf_bitpattern[offset + dummy[j]]) << 1, 17, pattern, bp);
        }

        if (symbol->symbology != BARCODE_PDF417COMP) {
            bp = z_bin_append_posn(((int) zint_pdf_bitpattern[offset + dummy[j]]) << 1, 17, pattern, bp);
            bp = z_bin_append_posn(0x3FA29, 18, pattern, bp); /* Row Stop */
        } else {
            pattern[bp++] = '1'; /* Compact PDF417 Stop pattern */
        }

        for (loop = 0; loop < bp; loop++) {
            if (pattern[loop] == '1') {
                z_set_module(symbol, i, loop);
            }
        }
    }
    symbol->width = bp;
    symbol->rows = rows;

    /* ISO/IEC 15438:2015 Section 5.8.2 3X minimum row height */
    if (error_number) {
        (void) z_set_height(symbol, 3.0f, 0.0f, 0.0f, 1 /*no_errtxt*/);
    } else {
        error_number = z_set_height(symbol, 3.0f, 0.0f, 0.0f, 0 /*no_errtxt*/);
    }

    /* 843 */
    return error_number;
}

/* 345 */
INTERNAL int zint_pdf417(struct zint_symbol *symbol, struct zint_seg segs[], const int seg_count) {
    int codeerr, error_number;

    error_number = 0;

    if (symbol->option_1 < -1 || symbol->option_1 > 8) {
        z_errtxtf(0, symbol, 460, "Error correction level '%d' out of range (0 to 8)", symbol->option_1);
        if ((symbol->warn_level & 0xFF) == WARN_FAIL_ALL) {
            return ZINT_ERROR_INVALID_OPTION;
        }
        error_number = z_errtxt_adj(ZINT_WARN_INVALID_OPTION, symbol, "%1$s%2$s", ", ignoring");
        symbol->option_1 = -1;
    }
    if (symbol->option_2 < 0 || symbol->option_2 > 30) {
        z_errtxtf(0, symbol, 461, "Number of columns '%d' out of range (1 to 30)", symbol->option_2);
        if ((symbol->warn_level & 0xFF) == WARN_FAIL_ALL) {
            return ZINT_ERROR_INVALID_OPTION;
        }
        error_number = z_errtxt_adj(ZINT_WARN_INVALID_OPTION, symbol, "%1$s%2$s", ", ignoring");
        symbol->option_2 = 0;
    }
    if (symbol->option_3 && (symbol->option_3 < 3 || symbol->option_3 > 90)) {
        return z_errtxtf(ZINT_ERROR_INVALID_OPTION, symbol, 466, "Number of rows '%d' out of range (3 to 90)",
                        symbol->option_3);
    }
    if (symbol->option_2 && symbol->option_3 && symbol->option_2 * symbol->option_3 > 928) {
        return z_errtxtf(ZINT_ERROR_INVALID_OPTION, symbol, 475, "Columns x rows value '%d' out of range (1 to 928)",
                        symbol->option_2 * symbol->option_3);
    }

    /* 349 */
    codeerr = pdf_enc(symbol, segs, seg_count);

    /* 352 */
    if (codeerr != 0) {
        error_number = codeerr;
    }

    /* 364 */
    return error_number;
}

/* Return variant based on `cols` and codeword length `mclength`. Returns 0 if `cols` 0 or no variant fits */
INTERNAL int zint_micropdf_variant(const int cols, const int mclength) {
    int variant = 0;
    if (cols == 1) {
        if (mclength <= 4) {
            variant = 1;
        } else if (mclength <= 7) {
            variant = 2;
        } else if (mclength <= 10) {
            variant = 3;
        } else if (mclength <= 12) {
            variant = 4;
        } else if (mclength <= 16) {
            variant = 5;
        } else {
            variant = 6;
        }
    } else if (cols == 2) {
        if (mclength <= 8) {
            variant = 7;
        } else if (mclength <= 13) {
            variant = 8;
        } else if (mclength <= 19) {
            variant = 9;
        } else if (mclength <= 24) {
            variant = 10;
        } else if (mclength <= 29) {
            variant = 11;
        } else if (mclength <= 33) {
            variant = 12;
        } else {
            variant = 13;
        }
    } else if (cols == 3) {
        if (mclength <= 6) {
            variant = 14;
        } else if (mclength <= 10) {
            variant = 15;
        } else if (mclength <= 14) {
            variant = 16;
        } else if (mclength <= 18) {
            variant = 17;
        } else if (mclength <= 24) {
            variant = 18;
        } else if (mclength <= 34) {
            variant = 19;
        } else if (mclength <= 46) {
            variant = 20;
        } else if (mclength <= 58) {
            variant = 21;
        } else if (mclength <= 70) {
            variant = 22;
        } else {
            variant = 23;
        }
    } else if (cols == 4) {
        if (mclength <= 8) {
            variant = 24;
        } else if (mclength <= 12) {
            variant = 25;
        } else if (mclength <= 18) {
            variant = 26;
        } else if (mclength <= 24) {
            variant = 27;
        } else if (mclength <= 30) {
            variant = 28;
        } else if (mclength <= 39) {
            variant = 29;
        } else if (mclength <= 54) {
            variant = 30;
        } else if (mclength <= 72) {
            variant = 31;
        } else if (mclength <= 90) {
            variant = 32;
        } else if (mclength <= 108) {
            variant = 33;
        } else {
            variant = 34;
        }
    }
    return variant;
}

INTERNAL void zint_micropdf_expand(struct zint_symbol *symbol, short *chainemc, int mclength, char *pattern,
                const int cols, const int variant, const int debug_print) {
    int bp = 0; /* Suppress gcc warning -Wmaybe-uninitialized */
    int i, k, j, mccorrection[50] = {0}, ecc_cwds;
    int offset = zint_pdf_MicroVariants[3][variant]; /* Coefficient offset */
    int LeftRAP = zint_pdf_RAPTable[0][variant] - 1;
    int CentreRAP = zint_pdf_RAPTable[1][variant] - 1;
    int RightRAP = zint_pdf_RAPTable[2][variant] - 1;
    int Cluster;

    /* Reed-Solomon error correction */
    ecc_cwds = zint_pdf_MicroVariants[2][variant]; /* Number of EC CWs */
    assert(ecc_cwds >= 7); /* Suppress clang-tidy-23 warning clang-analyzer-security.ArrayBound */

    for (i = 0; i < mclength; i++) {
        const int total = (chainemc[i] + mccorrection[ecc_cwds - 1]) % 929;
        for (j = ecc_cwds - 1; j >= 0; j--) {
            if (j == 0) {
                mccorrection[j] = (929 - (total * zint_pdf_Microcoeffs[offset + j]) % 929) % 929;
            } else {
                mccorrection[j] = (mccorrection[j - 1] + 929 - (total * zint_pdf_Microcoeffs[offset + j]) % 929)
                                    % 929;
            }
        }
    }

    for (j = 0; j < ecc_cwds; j++) {
        if (mccorrection[j] != 0) {
            mccorrection[j] = 929 - mccorrection[j];
        }
    }
    /* We add these codes to the string */
    for (i = ecc_cwds - 1; i >= 0; i--) {
        chainemc[mclength++] = mccorrection[i];
    }

    if (debug_print) {
        printf("Encoded Data Stream with ECC (%d):\n", mclength);
        for (i = 0; i < mclength; i++) {
            printf("%3d ", chainemc[i]);
        }
        fputc('\n', stdout);
    }
#ifdef ZINT_TEST
    if (symbol->debug & ZINT_DEBUG_TEST) {
        z_debug_test_codeword_dump_short(symbol, chainemc, mclength);
    }
#endif

    /* Cluster can be 0, 1 or 2 for Cluster(0), Cluster(3) and Cluster(6) */
    Cluster = zint_pdf_RAPTable[3][variant]; /* Pre-divided by 3 */

    /* That's all values loaded, get on with the encoding */

    if (debug_print) fputs("\nInternal row representation:\n", stdout);

    for (i = 0; i < symbol->rows; i++) {
        if (debug_print) printf("row %d: ", i);
        bp = 0;
        offset = 929 * Cluster;
        k = i * cols;

        /* Copy the data into codebarre */
        bp = z_bin_append_posn(zint_pdf_rap_side[LeftRAP], 10, pattern, bp);
        bp = z_bin_append_posn(((int) zint_pdf_bitpattern[offset + chainemc[k]]) << 1, 17, pattern, bp);
        if (cols >= 2) {
            if (cols == 3) {
                bp = z_bin_append_posn(zint_pdf_rap_centre[CentreRAP], 10, pattern, bp);
            }
            bp = z_bin_append_posn(((int) zint_pdf_bitpattern[offset + chainemc[k + 1]]) << 1, 17, pattern, bp);
            if (cols >= 3) {
                if (cols == 4) {
                    bp = z_bin_append_posn(zint_pdf_rap_centre[CentreRAP], 10, pattern, bp);
                }
                bp = z_bin_append_posn(((int) zint_pdf_bitpattern[offset + chainemc[k + 2]]) << 1, 17, pattern, bp);
                if (cols == 4) {
                    bp = z_bin_append_posn(((int) zint_pdf_bitpattern[offset + chainemc[k + 3]]) << 1, 17, pattern,
                                            bp);
                }
            }
        }
        bp = z_bin_append_posn(zint_pdf_rap_side[RightRAP], 10, pattern, bp);
        pattern[bp++] = '1'; /* Stop */
        if (debug_print) printf("%.*s\n", bp, pattern);

        /* So now pattern[] holds the string of '1's and '0's. - copy this to the symbol */
        for (j = 0; j < bp; j++) {
            if (pattern[j] == '1') {
                z_set_module(symbol, i, j);
            }
        }

        /* Set up RAPs and Cluster for next row */
        LeftRAP = LeftRAP == 51 ? 0 : LeftRAP + 1;
        CentreRAP = CentreRAP == 51 ? 0 : CentreRAP + 1;
        RightRAP = RightRAP == 51 ? 0 : RightRAP + 1;
        Cluster = Cluster == 2 ? 0 : Cluster + 1;
    }
    symbol->width = bp;
}

/* Like PDF417 only much smaller! */
INTERNAL int zint_micropdf417(struct zint_symbol *symbol, struct zint_seg segs[], const int seg_count) {
    int i, longueur, padding;
    int ecc_cwds;
    int mclength, error_number = 0;
    short chainemc[PDF_MAX_STREAM_LEN];
    char pattern[580];
    int structapp_cws[18] = {0}; /* 3 (Index) + 10 (ID) + 4 (Count) + 1 (Last) */
    int structapp_cp = 0;
    int variant;
    const int debug_print = symbol->debug & ZINT_DEBUG_PRINT;
    /* From ISO/IEC 24728:2006 Table 1 — MicroPDF417 version characteristics */
    static char col_max_codewords[5] = { 0, 20, 37, 82, 126 };

    if ((i = z_segs_length(segs, seg_count)) > MICRO_PDF_MAX_LEN) {
        return z_errtxtf(ZINT_ERROR_TOO_LONG, symbol, 474,
                        "Input length %d too long (maximum " MICRO_PDF_MAX_LEN_S ")", i);
    }
    if (symbol->option_3) {
        return z_errtxt(ZINT_ERROR_INVALID_OPTION, symbol, 476, "Cannot specify rows for MicroPDF417");
    }

    /* Encoding starts out the same as PDF417, so use the same code */

    if ((error_number = pdf_initial_segs(symbol, segs, seg_count, 1 /*is_micro*/, chainemc, &mclength, structapp_cws,
                                        &structapp_cp))) {
        assert(error_number >= ZINT_ERROR);
        return error_number;
    }

    /* This is where it all changes! */

    if (mclength + structapp_cp > 126) {
        return z_errtxtf(ZINT_ERROR_TOO_LONG, symbol, 467, "Input too long, requires %d codewords (maximum 126)",
                        mclength + structapp_cp);
    }
    if (symbol->option_2 > 4) {
        if ((symbol->warn_level & 0xFF) == WARN_FAIL_ALL) {
            return z_errtxtf(ZINT_ERROR_INVALID_OPTION, symbol, 471, "Number of columns '%d' out of range (1 to 4)",
                            symbol->option_2);
        }
        error_number = z_errtxtf(ZINT_WARN_INVALID_OPTION, symbol, 468,
                                "Number of columns '%d' out of range (1 to 4), ignoring", symbol->option_2);
        symbol->option_2 = 0;
    }

    if (debug_print) {
        printf("\nEncoded Data Stream (%d):\n", mclength);
        for (i = 0; i < mclength; i++) {
            printf("%3d ", chainemc[i]);
        }
        fputc('\n', stdout);
    }

    /* Now figure out which variant of the symbol to use and load values accordingly */

    if (symbol->option_2 >= 1 && mclength + structapp_cp > col_max_codewords[symbol->option_2]) {
        /* The user specified the column but the data doesn't fit - go to automatic */
        if ((symbol->warn_level & 0xFF) == WARN_FAIL_ALL) {
            return ZEXT z_errtxtf(ZINT_ERROR_INVALID_OPTION, symbol, 469,
                                "Input too long for number of columns '%1$d', requires %2$d codewords (maximum %3$d)",
                                symbol->option_2, mclength + structapp_cp, col_max_codewords[symbol->option_2]);
        }
        error_number = z_errtxtf(ZINT_WARN_INVALID_OPTION, symbol, 470,
                                "Input too long for number of columns '%d', ignoring", symbol->option_2);
        symbol->option_2 = 0;
    }

    /* If the user didn't specify column or the data doesn't fit */
    if (!(variant = zint_micropdf_variant(symbol->option_2, mclength + structapp_cp))) {
        /* Zint can choose automatically from all available variations */
        for (i = 27; i >= 0; i--) {
            /* Note mclength + structapp_cp <= 126 and pdf_MicroAutosize[27] == 126 so variant will be set */
            if (pdf_MicroAutosize[i] >= mclength + structapp_cp) {
                variant = pdf_MicroAutosize[i + 28];
            } else {
                break;
            }
        }
    }
    assert(variant > 0); /* Suppress clang-tidy-23 warning clang-analyzer-core.uninitialized.Assign */

    /* Now we have the variant we can load the data */
    variant--;
    symbol->option_2 = zint_pdf_MicroVariants[0][variant]; /* Columns */
    symbol->rows = zint_pdf_MicroVariants[1][variant]; /* Rows */
    ecc_cwds = zint_pdf_MicroVariants[2][variant]; /* Number of EC CWs */

    longueur = (symbol->option_2 * symbol->rows) - ecc_cwds; /* Number of non-EC CWs */
    padding = longueur - (mclength + structapp_cp); /* Amount of padding required */

    /* Feedback options */
    /* Place in top byte, leaving bottom one for maybe future use - also compatible with AZTEC */
    symbol->option_1 = ((int) z_stripf(roundf(z_stripf(ecc_cwds * 100.0f / (longueur + ecc_cwds))))) << 8;

    if (debug_print) {
        fputs("\nChoose symbol size:\n", stdout);
        printf("%d columns x %d rows, variant %d\n", symbol->option_2, symbol->rows, variant + 1);
        printf("%d data codewords (including %d pads), %d ecc codewords\n", longueur, padding, ecc_cwds);
        fputc('\n', stdout);
    }

    /* We add the padding */
    while (padding-- > 0) {
        chainemc[mclength++] = 900;
    }

    /* We add the Structured Append Macro Control Block if any */
    if (structapp_cp) {
        for (i = 0; i < structapp_cp; i++) {
            chainemc[mclength++] = structapp_cws[i];
        }
    }

    zint_micropdf_expand(symbol, chainemc, mclength, pattern, symbol->option_2, variant, debug_print);

    /* ISO/IEC 24728:2006 Section 5.8.2 2X minimum row height */
    if (error_number) {
        (void) z_set_height(symbol, 2.0f, 0.0f, 0.0f, 1 /*no_errtxt*/);
    } else {
        error_number = z_set_height(symbol, 2.0f, 0.0f, 0.0f, 0 /*no_errtxt*/);
    }

    return error_number;
}

#undef T_ALPHA
#undef T_LOWER
#undef T_MIXED
#undef T_PUNCT
#undef T_ALWMX
#undef T_MXPNC

/* vim: set ts=4 sw=4 et : */
