diff mupdf-source/thirdparty/zint/backend/hanxin.c @ 2:b50eed0cc0ef upstream

ADD: MuPDF v1.26.7: the MuPDF source as downloaded by a default build of PyMuPDF 1.26.4. The directory name has changed: no version number in the expanded directory now.
author Franz Glasner <fzglas.hg@dom66.de>
date Mon, 15 Sep 2025 11:43:07 +0200
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mupdf-source/thirdparty/zint/backend/hanxin.c	Mon Sep 15 11:43:07 2025 +0200
@@ -0,0 +1,1695 @@
+/*  hanxin.c - Han Xin Code */
+/*
+    libzint - the open source barcode library
+    Copyright (C) 2009-2024 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.
+ */
+/* SPDX-License-Identifier: BSD-3-Clause */
+
+/* This code attempts to implement Han Xin Code according to ISO/IEC 20830:2021
+ * (previously ISO/IEC 20830 (draft 2019-10-10) and AIMD-015:2010 (Rev 0.8)) */
+
+#include <assert.h>
+#include <stdio.h>
+#include "common.h"
+#include "reedsol.h"
+#include "hanxin.h"
+#include "eci.h"
+
+/* Find which submode to use for a text character */
+static int hx_getsubmode(const unsigned int input) {
+
+    if (z_isdigit(input)) {
+        return 1;
+    }
+
+    if (z_isupper(input)) {
+        return 1;
+    }
+
+    if (z_islower(input)) {
+        return 1;
+    }
+
+    return 2;
+}
+
+/* Return length of terminator for encoding mode */
+static int hx_terminator_length(const char mode) {
+    int result = 0;
+
+    switch (mode) {
+        case 'n':
+            result = 10;
+            break;
+        case 't':
+            result = 6;
+            break;
+        case '1':
+        case '2':
+            result = 12;
+            break;
+        case 'd':
+            result = 15;
+            break;
+    }
+
+    return result;
+}
+
+/* Calculate the length of the binary string */
+static int hx_calc_binlen(const char mode[], const unsigned int ddata[], const int length, const int eci) {
+    int i;
+    char lastmode = '\0';
+    int est_binlen = 0;
+    int submode = 1;
+    int numeric_run = 0;
+
+    if (eci != 0) {
+        est_binlen += 4;
+        if (eci <= 127) {
+            est_binlen += 8;
+        } else if (eci <= 16383) {
+            est_binlen += 16;
+        } else {
+            est_binlen += 24;
+        }
+    }
+
+    i = 0;
+    do {
+        if (mode[i] != lastmode) {
+            if (i > 0) {
+                est_binlen += hx_terminator_length(lastmode);
+            }
+            /* GB 4-byte has indicator for each character (and no terminator) so not included here */
+            /* Region1/Region2 have special terminator to go directly into each other's mode so not included here */
+            if (mode[i] != 'f' || ((mode[i] == '1' && lastmode == '2') || (mode[i] == '2' && lastmode == '1'))) {
+                est_binlen += 4;
+            }
+            if (mode[i] == 'b') { /* Byte mode has byte count (and no terminator) */
+                est_binlen += 13;
+            }
+            lastmode = mode[i];
+            submode = 1;
+            numeric_run = 0;
+        }
+        switch (mode[i]) {
+            case 'n':
+                if (numeric_run % 3 == 0) {
+                    est_binlen += 10;
+                }
+                numeric_run++;
+                break;
+            case 't':
+                if (hx_getsubmode(ddata[i]) != submode) {
+                    est_binlen += 6;
+                    submode = hx_getsubmode(ddata[i]);
+                }
+                est_binlen += 6;
+                break;
+            case 'b':
+                est_binlen += ddata[i] > 0xFF ? 16 : 8;
+                break;
+            case '1':
+            case '2':
+                est_binlen += 12;
+                break;
+            case 'd':
+                est_binlen += 15;
+                break;
+            case 'f':
+                est_binlen += 25;
+                i++;
+                break;
+        }
+        i++;
+    } while (i < length);
+
+    est_binlen += hx_terminator_length(lastmode);
+
+    return est_binlen;
+}
+
+/* Call `hx_calc_binlen()` for each segment */
+static int hx_calc_binlen_segs(const char mode[], const unsigned int ddata[], const struct zint_seg segs[],
+            const int seg_count) {
+    int i;
+    int count = 0;
+    const unsigned int *dd = ddata;
+    const char *m = mode;
+
+    for (i = 0; i < seg_count; i++) {
+        count += hx_calc_binlen(m, dd, segs[i].length, segs[i].eci);
+        m += segs[i].length;
+        dd += segs[i].length;
+    }
+
+    return count;
+}
+
+static int hx_isRegion1(const unsigned int glyph) {
+    unsigned int byte;
+
+    byte = glyph >> 8;
+
+    if ((byte >= 0xb0) && (byte <= 0xd7)) {
+        byte = glyph & 0xff;
+        if ((byte >= 0xa1) && (byte <= 0xfe)) {
+            return 1;
+        }
+    } else if ((byte >= 0xa1) && (byte <= 0xa3)) {
+        byte = glyph & 0xff;
+        if ((byte >= 0xa1) && (byte <= 0xfe)) {
+            return 1;
+        }
+    } else if ((glyph >= 0xa8a1) && (glyph <= 0xa8c0)) {
+        return 1;
+    }
+
+    return 0;
+}
+
+static int hx_isRegion2(const unsigned int glyph) {
+    unsigned int byte;
+
+    byte = glyph >> 8;
+
+    if ((byte >= 0xd8) && (byte <= 0xf7)) {
+        byte = glyph & 0xff;
+        if ((byte >= 0xa1) && (byte <= 0xfe)) {
+            return 1;
+        }
+    }
+
+    return 0;
+}
+
+static int hx_isDoubleByte(const unsigned int glyph) {
+    unsigned int byte;
+
+    byte = glyph >> 8;
+
+    if ((byte >= 0x81) && (byte <= 0xfe)) {
+        byte = glyph & 0xff;
+        if ((byte >= 0x40) && (byte <= 0x7e)) {
+            return 1;
+        }
+        if ((byte >= 0x80) && (byte <= 0xfe)) {
+            return 1;
+        }
+    }
+
+    return 0;
+}
+
+static int hx_isFourByte(const unsigned int glyph, const unsigned int glyph2) {
+    unsigned int byte;
+
+    byte = glyph >> 8;
+
+    if ((byte >= 0x81) && (byte <= 0xfe)) {
+        byte = glyph & 0xff;
+        if ((byte >= 0x30) && (byte <= 0x39)) {
+            byte = glyph2 >> 8;
+            if ((byte >= 0x81) && (byte <= 0xfe)) {
+                byte = glyph2 & 0xff;
+                if ((byte >= 0x30) && (byte <= 0x39)) {
+                    return 1;
+                }
+            }
+        }
+    }
+
+    return 0;
+}
+
+/* Convert Text 1 sub-mode character to encoding value, as given in table 3 */
+static int hx_lookup_text1(const unsigned int input) {
+
+    if (z_isdigit(input)) {
+        return input - '0';
+    }
+
+    if (z_isupper(input)) {
+        return input - 'A' + 10;
+    }
+
+    if (z_islower(input)) {
+        return input - 'a' + 36;
+    }
+
+    return -1;
+}
+
+/* Convert Text 2 sub-mode character to encoding value, as given in table 4 */
+static int hx_lookup_text2(const unsigned int input) {
+
+    if (input <= 27) {
+        return input;
+    }
+
+    if ((input >= ' ') && (input <= '/')) {
+        return input - ' ' + 28;
+    }
+
+    if ((input >= ':') && (input <= '@')) {
+        return input - ':' + 44;
+    }
+
+    if ((input >= '[') && (input <= 96)) {
+        return input - '[' + 51;
+    }
+
+    if ((input >= '{') && (input <= 127)) {
+        return input - '{' + 57;
+    }
+
+    return -1;
+}
+
+/* hx_define_mode() stuff */
+
+/* Bits multiplied by this for costs, so as to be whole integer divisible by 2 and 3 */
+#define HX_MULT 6
+
+/* Whether in numeric or not. If in numeric, *p_end is set to position after numeric,
+ * and *p_cost is set to per-numeric cost */
+static int hx_in_numeric(const unsigned int ddata[], const int length, const int in_posn,
+            unsigned int *p_end, unsigned int *p_cost) {
+    int i, digit_cnt;
+
+    if (in_posn < (int) *p_end) {
+        return 1;
+    }
+
+    /* Attempt to calculate the average 'cost' of using numeric mode in number of bits (times HX_MULT) */
+    for (i = in_posn; i < length && i < in_posn + 3 && z_isdigit(ddata[i]); i++);
+
+    digit_cnt = i - in_posn;
+
+    if (digit_cnt == 0) {
+        *p_end = 0;
+        return 0;
+    }
+    *p_end = i;
+    *p_cost = digit_cnt == 1
+                ? 60 /* 10 * HX_MULT */ : digit_cnt == 2 ? 30 /* (10 / 2) * HX_MULT */ : 20 /* (10 / 3) * HX_MULT */;
+    return 1;
+}
+
+/* Whether in four-byte or not. If in four-byte, *p_fourbyte is set to position after four-byte,
+ * and *p_fourbyte_cost is set to per-position cost */
+static int hx_in_fourbyte(const unsigned int ddata[], const int length, const int in_posn,
+            unsigned int *p_end, unsigned int *p_cost) {
+    if (in_posn < (int) *p_end) {
+        return 1;
+    }
+
+    if (in_posn == length - 1 || !hx_isFourByte(ddata[in_posn], ddata[in_posn + 1])) {
+        *p_end = 0;
+        return 0;
+    }
+    *p_end = in_posn + 2;
+    *p_cost = 75; /* ((4 + 21) / 2) * HX_MULT */
+    return 1;
+}
+
+/* Indexes into mode_types array */
+#define HX_N   0 /* Numeric */
+#define HX_T   1 /* Text */
+#define HX_B   2 /* Binary */
+#define HX_1   3 /* Common Chinese Region One */
+#define HX_2   4 /* Common Chinese Region Two */
+#define HX_D   5 /* GB 18030 2-byte Region */
+#define HX_F   6 /* GB 18030 4-byte Region */
+/* Note Unicode, GS1 and URI modes not implemented */
+
+#define HX_NUM_MODES 7
+
+/* Calculate optimized encoding modes. Adapted from Project Nayuki */
+/* Copyright (c) Project Nayuki. (MIT License) See qr.c for detailed notice */
+static void hx_define_mode(char *mode, const unsigned int ddata[], const int length, const int debug_print) {
+    /* Must be in same order as HX_N etc */
+    static const char mode_types[] = { 'n', 't', 'b', '1', '2', 'd', 'f', '\0' };
+
+    /* Initial mode costs */
+    static const unsigned int head_costs[HX_NUM_MODES] = {
+    /*  N            T            B                   1            2            D            F */
+        4 * HX_MULT, 4 * HX_MULT, (4 + 13) * HX_MULT, 4 * HX_MULT, 4 * HX_MULT, 4 * HX_MULT, 0
+    };
+
+    /* Cost of switching modes from k to j */
+    static const unsigned char switch_costs[HX_NUM_MODES][HX_NUM_MODES] = {
+        /*      N                   T                   B                        1                   2                   D                   F */
+        /*N*/ {                  0, (10 + 4) * HX_MULT, (10 + 4 + 13) * HX_MULT, (10 + 4) * HX_MULT, (10 + 4) * HX_MULT, (10 + 4) * HX_MULT, 10 * HX_MULT },
+        /*T*/ {  (6 + 4) * HX_MULT,                  0,  (6 + 4 + 13) * HX_MULT,  (6 + 4) * HX_MULT,  (6 + 4) * HX_MULT,  (6 + 4) * HX_MULT,  6 * HX_MULT },
+        /*B*/ {        4 * HX_MULT,        4 * HX_MULT,                       0,        4 * HX_MULT,        4 * HX_MULT,        4 * HX_MULT,  0 },
+        /*1*/ { (12 + 4) * HX_MULT, (12 + 4) * HX_MULT, (12 + 4 + 13) * HX_MULT,                  0,       12 * HX_MULT, (12 + 4) * HX_MULT, 12 * HX_MULT },
+        /*2*/ { (12 + 4) * HX_MULT, (12 + 4) * HX_MULT, (12 + 4 + 13) * HX_MULT,       12 * HX_MULT,                  0, (12 + 4) * HX_MULT, 12 * HX_MULT },
+        /*D*/ { (15 + 4) * HX_MULT, (15 + 4) * HX_MULT, (15 + 4 + 13) * HX_MULT, (15 + 4) * HX_MULT, (15 + 4) * HX_MULT,                  0, 15 * HX_MULT },
+        /*F*/ {        4 * HX_MULT,        4 * HX_MULT,      (4 + 13) * HX_MULT,        4 * HX_MULT,        4 * HX_MULT,        4 * HX_MULT,  0 },
+    };
+
+    /* Final end-of-data costs */
+    static const unsigned char eod_costs[HX_NUM_MODES] = {
+    /*  N             T            B  1             2             D             F */
+        10 * HX_MULT, 6 * HX_MULT, 0, 12 * HX_MULT, 12 * HX_MULT, 15 * HX_MULT, 0
+    };
+
+    unsigned int numeric_end = 0, numeric_cost = 0, text_submode = 1, fourbyte_end = 0, fourbyte_cost = 0; /* State */
+    int text1, text2;
+
+    int i, j, k;
+    unsigned int min_cost;
+    char cur_mode;
+    unsigned int prev_costs[HX_NUM_MODES];
+    unsigned int cur_costs[HX_NUM_MODES];
+    char (*char_modes)[HX_NUM_MODES] = (char (*)[HX_NUM_MODES]) z_alloca(HX_NUM_MODES * length);
+
+    /* char_modes[i][j] represents the mode to encode the code point at index i such that the final segment
+       ends in mode_types[j] and the total number of bits is minimized over all possible choices */
+    memset(char_modes, 0, HX_NUM_MODES * length);
+
+    /* At the beginning of each iteration of the loop below, prev_costs[j] is the minimum number of 1/6 (1/XX_MULT)
+     * bits needed to encode the entire string prefix of length i, and end in mode_types[j] */
+    memcpy(prev_costs, head_costs, HX_NUM_MODES * sizeof(unsigned int));
+
+    /* Calculate costs using dynamic programming */
+    for (i = 0; i < length; i++) {
+        memset(cur_costs, 0, HX_NUM_MODES * sizeof(unsigned int));
+
+        if (hx_in_numeric(ddata, length, i, &numeric_end, &numeric_cost)) {
+            cur_costs[HX_N] = prev_costs[HX_N] + numeric_cost;
+            char_modes[i][HX_N] = 'n';
+            text1 = 1;
+            text2 = 0;
+        } else {
+            text1 = hx_lookup_text1(ddata[i]) != -1;
+            text2 = hx_lookup_text2(ddata[i]) != -1;
+        }
+
+        if (text1 || text2) {
+            if ((text_submode == 1 && text2) || (text_submode == 2 && text1)) {
+                cur_costs[HX_T] = prev_costs[HX_T] + 72; /* (6 + 6) * HX_MULT */
+                text_submode = text2 ? 2 : 1;
+            } else {
+                cur_costs[HX_T] = prev_costs[HX_T] + 36; /* 6 * HX_MULT */
+            }
+            char_modes[i][HX_T] = 't';
+        } else {
+            text_submode = 1;
+        }
+
+        /* Binary mode can encode anything */
+        cur_costs[HX_B] = prev_costs[HX_B] + (ddata[i] > 0xFF ? 96 : 48); /* (16 : 8) * HX_MULT */
+        char_modes[i][HX_B] = 'b';
+
+        if (hx_in_fourbyte(ddata, length, i, &fourbyte_end, &fourbyte_cost)) {
+            cur_costs[HX_F] = prev_costs[HX_F] + fourbyte_cost;
+            char_modes[i][HX_F] = 'f';
+        } else {
+            if (hx_isDoubleByte(ddata[i])) {
+                cur_costs[HX_D] = prev_costs[HX_D] + 90; /* 15 * HX_MULT */
+                char_modes[i][HX_D] = 'd';
+                if (hx_isRegion1(ddata[i])) { /* Subset */
+                    cur_costs[HX_1] = prev_costs[HX_1] + 72; /* 12 * HX_MULT */
+                    char_modes[i][HX_1] = '1';
+                } else if (hx_isRegion2(ddata[i])) { /* Subset */
+                    cur_costs[HX_2] = prev_costs[HX_2] + 72; /* 12 * HX_MULT */
+                    char_modes[i][HX_2] = '2';
+                }
+            }
+        }
+
+        if (i == length - 1) { /* Add end of data costs if last character */
+            for (j = 0; j < HX_NUM_MODES; j++) {
+                if (char_modes[i][j]) {
+                    cur_costs[j] += eod_costs[j];
+                }
+            }
+        }
+
+        /* Start new segment at the end to switch modes */
+        for (j = 0; j < HX_NUM_MODES; j++) { /* To mode */
+            for (k = 0; k < HX_NUM_MODES; k++) { /* From mode */
+                if (j != k && char_modes[i][k]) {
+                    const unsigned int new_cost = cur_costs[k] + switch_costs[k][j];
+                    if (!char_modes[i][j] || new_cost < cur_costs[j]) {
+                        cur_costs[j] = new_cost;
+                        char_modes[i][j] = mode_types[k];
+                    }
+                }
+            }
+        }
+
+        memcpy(prev_costs, cur_costs, HX_NUM_MODES * sizeof(unsigned int));
+    }
+
+    /* Find optimal ending mode */
+    min_cost = prev_costs[0];
+    cur_mode = mode_types[0];
+    for (i = 1; i < HX_NUM_MODES; i++) {
+        if (prev_costs[i] < min_cost) {
+            min_cost = prev_costs[i];
+            cur_mode = mode_types[i];
+        }
+    }
+
+    /* Get optimal mode for each code point by tracing backwards */
+    for (i = length - 1; i >= 0; i--) {
+        j = posn(mode_types, cur_mode);
+        cur_mode = char_modes[i][j];
+        mode[i] = cur_mode;
+    }
+
+    if (debug_print) {
+        printf("  Mode: %.*s\n", length, mode);
+    }
+}
+
+/* Call `hx_define_mode()` for each segment */
+static void hx_define_mode_segs(char mode[], const unsigned int ddata[], const struct zint_seg segs[],
+            const int seg_count, const int debug_print) {
+    int i;
+    const unsigned int *dd = ddata;
+    char *m = mode;
+
+    for (i = 0; i < seg_count; i++) {
+        hx_define_mode(m, dd, segs[i].length, debug_print);
+        m += segs[i].length;
+        dd += segs[i].length;
+    }
+}
+
+/* Convert input data to binary stream */
+static void hx_calculate_binary(char binary[], const char mode[], const unsigned int ddata[], const int length,
+            const int eci, int *p_bp, const int debug_print) {
+    int position = 0;
+    int i, count, encoding_value;
+    int first_byte, second_byte;
+    int third_byte, fourth_byte;
+    int glyph;
+    int submode;
+    int bp = *p_bp;
+
+    if (eci != 0) {
+        /* Encoding ECI assignment number, according to Table 5 */
+        bp = bin_append_posn(8, 4, binary, bp); /* ECI */
+        if (eci <= 127) {
+            bp = bin_append_posn(eci, 8, binary, bp);
+        } else if (eci <= 16383) {
+            bp = bin_append_posn(2, 2, binary, bp);
+            bp = bin_append_posn(eci, 14, binary, bp);
+        } else {
+            bp = bin_append_posn(6, 3, binary, bp);
+            bp = bin_append_posn(eci, 21, binary, bp);
+        }
+    }
+
+    do {
+        int block_length = 0;
+        int double_byte = 0;
+        do {
+            if (mode[position] == 'b' && ddata[position + block_length] > 0xFF) {
+                double_byte++;
+            }
+            block_length++;
+        } while (position + block_length < length && mode[position + block_length] == mode[position]);
+
+        switch (mode[position]) {
+            case 'n':
+                /* Numeric mode */
+                /* Mode indicator */
+                bp = bin_append_posn(1, 4, binary, bp);
+
+                if (debug_print) {
+                    printf("Numeric (N%d): ", block_length);
+                }
+
+                count = 0; /* Suppress gcc -Wmaybe-uninitialized */
+                i = 0;
+
+                while (i < block_length) {
+                    const int first = ctoi((const char) ddata[position + i]);
+                    count = 1;
+                    encoding_value = first;
+
+                    if (i + 1 < block_length && mode[position + i + 1] == 'n') {
+                        const int second = ctoi((const char) ddata[position + i + 1]);
+                        count = 2;
+                        encoding_value = (encoding_value * 10) + second;
+
+                        if (i + 2 < block_length && mode[position + i + 2] == 'n') {
+                            const int third = ctoi((const char) ddata[position + i + 2]);
+                            count = 3;
+                            encoding_value = (encoding_value * 10) + third;
+                        }
+                    }
+
+                    bp = bin_append_posn(encoding_value, 10, binary, bp);
+
+                    if (debug_print) {
+                        printf(" 0x%3x(%d)", encoding_value, encoding_value);
+                    }
+
+                    i += count;
+                }
+
+                /* Mode terminator depends on number of characters in last group (Table 2) */
+                switch (count) {
+                    case 1:
+                        bp = bin_append_posn(1021, 10, binary, bp);
+                        break;
+                    case 2:
+                        bp = bin_append_posn(1022, 10, binary, bp);
+                        break;
+                    case 3:
+                        bp = bin_append_posn(1023, 10, binary, bp);
+                        break;
+                }
+
+                if (debug_print) {
+                    printf(" (TERM %d)\n", count);
+                }
+
+                break;
+            case 't':
+                /* Text mode */
+                /* Mode indicator */
+                bp = bin_append_posn(2, 4, binary, bp);
+
+                if (debug_print) {
+                    printf("Text (T%d):", block_length);
+                }
+
+                submode = 1;
+
+                i = 0;
+
+                while (i < block_length) {
+
+                    if (hx_getsubmode(ddata[i + position]) != submode) {
+                        /* Change submode */
+                        bp = bin_append_posn(62, 6, binary, bp);
+                        submode = hx_getsubmode(ddata[i + position]);
+                        if (debug_print) {
+                            fputs(" SWITCH", stdout);
+                        }
+                    }
+
+                    if (submode == 1) {
+                        encoding_value = hx_lookup_text1(ddata[i + position]);
+                    } else {
+                        encoding_value = hx_lookup_text2(ddata[i + position]);
+                    }
+
+                    bp = bin_append_posn(encoding_value, 6, binary, bp);
+
+                    if (debug_print) {
+                        printf(" %.2x[ASC %.2x]", encoding_value, ddata[i + position]);
+                    }
+                    i++;
+                }
+
+                /* Terminator */
+                bp = bin_append_posn(63, 6, binary, bp);
+
+                if (debug_print) {
+                    fputs("\n", stdout);
+                }
+                break;
+            case 'b':
+                /* Binary Mode */
+                /* Mode indicator */
+                bp = bin_append_posn(3, 4, binary, bp);
+
+                /* Count indicator */
+                bp = bin_append_posn(block_length + double_byte, 13, binary, bp);
+
+                if (debug_print) {
+                    printf("Binary Mode (B%d):", block_length + double_byte);
+                }
+
+                i = 0;
+
+                while (i < block_length) {
+
+                    /* 8-bit bytes with no conversion */
+                    bp = bin_append_posn(ddata[i + position], ddata[i + position] > 0xFF ? 16 : 8, binary, bp);
+
+                    if (debug_print) {
+                        printf(" %02x", (int) ddata[i + position]);
+                    }
+
+                    i++;
+                }
+
+                if (debug_print) {
+                    fputs("\n", stdout);
+                }
+                break;
+            case '1':
+                /* Region One encoding */
+                /* Mode indicator */
+                if (position == 0 || mode[position - 1] != '2') { /* Unless previous mode Region Two */
+                    bp = bin_append_posn(4, 4, binary, bp);
+                }
+
+                if (debug_print) {
+                    printf("Region One%s H(1)%d:",
+                        position == 0 || mode[position - 1] != '2' ? "" : " (NO indicator)", block_length);
+                }
+
+                i = 0;
+
+                while (i < block_length) {
+                    first_byte = (ddata[i + position] & 0xff00) >> 8;
+                    second_byte = ddata[i + position] & 0xff;
+
+                    /* Subset 1 */
+                    glyph = (0x5e * (first_byte - 0xb0)) + (second_byte - 0xa1);
+
+                    /* Subset 2 */
+                    if ((first_byte >= 0xa1) && (first_byte <= 0xa3)) {
+                        if ((second_byte >= 0xa1) && (second_byte <= 0xfe)) {
+                            glyph = (0x5e * (first_byte - 0xa1)) + (second_byte - 0xa1) + 0xeb0;
+                        }
+                    }
+
+                    /* Subset 3 */
+                    if ((ddata[i + position] >= 0xa8a1) && (ddata[i + position] <= 0xa8c0)) {
+                        glyph = (second_byte - 0xa1) + 0xfca;
+                    }
+
+                    if (debug_print) {
+                        printf(" %.3x[GB %.4x]", glyph, ddata[i + position]);
+                    }
+
+                    bp = bin_append_posn(glyph, 12, binary, bp);
+                    i++;
+                }
+
+                /* Terminator */
+                bp = bin_append_posn(position + block_length == length || mode[position + block_length] != '2'
+                                    ? 4095 : 4094, 12, binary, bp);
+
+                if (debug_print) {
+                    printf(" (TERM %x)\n", position + block_length == length || mode[position + block_length] != '2'
+                            ? 4095 : 4094);
+                }
+
+                break;
+            case '2':
+                /* Region Two encoding */
+                /* Mode indicator */
+                if (position == 0 || mode[position - 1] != '1') { /* Unless previous mode Region One */
+                    bp = bin_append_posn(5, 4, binary, bp);
+                }
+
+                if (debug_print) {
+                    printf("Region Two%s H(2)%d:",
+                            position == 0 || mode[position - 1] != '1' ? "" : " (NO indicator)", block_length);
+                }
+
+                i = 0;
+
+                while (i < block_length) {
+                    first_byte = (ddata[i + position] & 0xff00) >> 8;
+                    second_byte = ddata[i + position] & 0xff;
+
+                    glyph = (0x5e * (first_byte - 0xd8)) + (second_byte - 0xa1);
+
+                    if (debug_print) {
+                        printf(" %.3x[GB %.4x]", glyph, ddata[i + position]);
+                    }
+
+                    bp = bin_append_posn(glyph, 12, binary, bp);
+                    i++;
+                }
+
+                /* Terminator */
+                bp = bin_append_posn(position + block_length == length || mode[position + block_length] != '1'
+                                    ? 4095 : 4094, 12, binary, bp);
+
+                if (debug_print) {
+                    printf(" (TERM %x)\n", position + block_length == length || mode[position + block_length] != '1'
+                            ? 4095 : 4094);
+                }
+
+                break;
+            case 'd':
+                /* Double byte encoding */
+                /* Mode indicator */
+                bp = bin_append_posn(6, 4, binary, bp);
+
+                if (debug_print) {
+                    printf("Double byte (H(d)%d):", block_length);
+                }
+
+                i = 0;
+
+                while (i < block_length) {
+                    first_byte = (ddata[i + position] & 0xff00) >> 8;
+                    second_byte = ddata[i + position] & 0xff;
+
+                    if (second_byte <= 0x7e) {
+                        glyph = (0xbe * (first_byte - 0x81)) + (second_byte - 0x40);
+                    } else {
+                        glyph = (0xbe * (first_byte - 0x81)) + (second_byte - 0x41);
+                    }
+
+                    if (debug_print) {
+                        printf("%.4x ", glyph);
+                    }
+
+                    bp = bin_append_posn(glyph, 15, binary, bp);
+                    i++;
+                }
+
+                /* Terminator */
+                bp = bin_append_posn(32767, 15, binary, bp);
+                /* Terminator sequence of length 12 is a mistake
+                   - confirmed by Wang Yi */
+
+                if (debug_print) {
+                    fputc('\n', stdout);
+                }
+                break;
+            case 'f':
+                /* Four-byte encoding */
+                if (debug_print) {
+                    printf("Four byte (H(f)%d):", block_length);
+                }
+
+                i = 0;
+
+                while (i < block_length) {
+
+                    /* Mode indicator */
+                    bp = bin_append_posn(7, 4, binary, bp);
+
+                    first_byte = (ddata[i + position] & 0xff00) >> 8;
+                    second_byte = ddata[i + position] & 0xff;
+                    third_byte = (ddata[i + position + 1] & 0xff00) >> 8;
+                    fourth_byte = ddata[i + position + 1] & 0xff;
+
+                    glyph = (0x3138 * (first_byte - 0x81)) + (0x04ec * (second_byte - 0x30)) +
+                            (0x0a * (third_byte - 0x81)) + (fourth_byte - 0x30);
+
+                    if (debug_print) {
+                        printf(" %d", glyph);
+                    }
+
+                    bp = bin_append_posn(glyph, 21, binary, bp);
+                    i += 2;
+                }
+
+                /* No terminator */
+
+                if (debug_print) {
+                    fputc('\n', stdout);
+                }
+                break;
+        }
+
+        position += block_length;
+
+    } while (position < length);
+
+    if (debug_print) printf("Binary (%d): %.*s\n", bp, bp, binary);
+
+    *p_bp = bp;
+}
+
+/* Call `hx_calculate_binary()` for each segment */
+static void hx_calculate_binary_segs(char binary[], const char mode[], const unsigned int ddata[],
+            const struct zint_seg segs[], const int seg_count, int *p_bin_len, const int debug_print) {
+    int i;
+    const unsigned int *dd = ddata;
+    const char *m = mode;
+    int bp = 0;
+
+    for (i = 0; i < seg_count; i++) {
+        hx_calculate_binary(binary, m, dd, segs[i].length, segs[i].eci, &bp, debug_print);
+        m += segs[i].length;
+        dd += segs[i].length;
+    }
+
+    *p_bin_len = bp;
+}
+
+/* Finder pattern for top left of symbol */
+static void hx_place_finder_top_left(unsigned char *grid, const int size) {
+    int xp, yp;
+    int x = 0, y = 0;
+    char finder[] = {0x7F, 0x40, 0x5F, 0x50, 0x57, 0x57, 0x57};
+
+    for (xp = 0; xp < 7; xp++) {
+        for (yp = 0; yp < 7; yp++) {
+            if (finder[yp] & 0x40 >> xp) {
+                grid[((yp + y) * size) + (xp + x)] = 0x11;
+            } else {
+                grid[((yp + y) * size) + (xp + x)] = 0x10;
+            }
+        }
+    }
+}
+
+/* Finder pattern for top right and bottom left of symbol */
+static void hx_place_finder(unsigned char *grid, const int size, const int x, const int y) {
+    int xp, yp;
+    static const char finder[] = {0x7F, 0x01, 0x7D, 0x05, 0x75, 0x75, 0x75};
+
+    for (xp = 0; xp < 7; xp++) {
+        for (yp = 0; yp < 7; yp++) {
+            if (finder[yp] & 0x40 >> xp) {
+                grid[((yp + y) * size) + (xp + x)] = 0x11;
+            } else {
+                grid[((yp + y) * size) + (xp + x)] = 0x10;
+            }
+        }
+    }
+}
+
+/* Finder pattern for bottom right of symbol */
+static void hx_place_finder_bottom_right(unsigned char *grid, const int size) {
+    int xp, yp;
+    const int x = size - 7;
+    const int y = x;
+    static const char finder[] = {0x75, 0x75, 0x75, 0x05, 0x7D, 0x01, 0x7F};
+
+    for (xp = 0; xp < 7; xp++) {
+        for (yp = 0; yp < 7; yp++) {
+            if (finder[yp] & 0x40 >> xp) {
+                grid[((yp + y) * size) + (xp + x)] = 0x11;
+            } else {
+                grid[((yp + y) * size) + (xp + x)] = 0x10;
+            }
+        }
+    }
+}
+
+/* Avoid plotting outside symbol or over finder patterns */
+static void hx_safe_plot(unsigned char *grid, const int size, const int x, const int y, const int value) {
+    if ((x >= 0) && (x < size)) {
+        if ((y >= 0) && (y < size)) {
+            if (grid[(y * size) + x] == 0) {
+                grid[(y * size) + x] = value;
+            }
+        }
+    }
+}
+
+/* Plot an alignment pattern around top and right of a module */
+static void hx_plot_alignment(unsigned char *grid, const int size, const int x, const int y, const int w,
+            const int h) {
+    int i;
+    hx_safe_plot(grid, size, x, y, 0x11);
+    hx_safe_plot(grid, size, x - 1, y + 1, 0x10);
+
+    for (i = 1; i <= w; i++) {
+        /* Top */
+        hx_safe_plot(grid, size, x - i, y, 0x11);
+        hx_safe_plot(grid, size, x - i - 1, y + 1, 0x10);
+    }
+
+    for (i = 1; i < h; i++) {
+        /* Right */
+        hx_safe_plot(grid, size, x, y + i, 0x11);
+        hx_safe_plot(grid, size, x - 1, y + i + 1, 0x10);
+    }
+}
+
+/* Plot assistant alignment patterns */
+static void hx_plot_assistant(unsigned char *grid, const int size, const int x, const int y) {
+    hx_safe_plot(grid, size, x - 1, y - 1, 0x10);
+    hx_safe_plot(grid, size, x, y - 1, 0x10);
+    hx_safe_plot(grid, size, x + 1, y - 1, 0x10);
+    hx_safe_plot(grid, size, x - 1, y, 0x10);
+    hx_safe_plot(grid, size, x, y, 0x11);
+    hx_safe_plot(grid, size, x + 1, y, 0x10);
+    hx_safe_plot(grid, size, x - 1, y + 1, 0x10);
+    hx_safe_plot(grid, size, x, y + 1, 0x10);
+    hx_safe_plot(grid, size, x + 1, y + 1, 0x10);
+}
+
+/* Put static elements in the grid */
+static void hx_setup_grid(unsigned char *grid, const int size, const int version) {
+    int i;
+
+    memset(grid, 0, (size_t) size * size);
+
+    /* Add finder patterns */
+    hx_place_finder_top_left(grid, size);
+    hx_place_finder(grid, size, 0, size - 7);
+    hx_place_finder(grid, size, size - 7, 0);
+    hx_place_finder_bottom_right(grid, size);
+
+    /* Add finder pattern separator region */
+    for (i = 0; i < 8; i++) {
+        /* Top left */
+        grid[(7 * size) + i] = 0x10;
+        grid[(i * size) + 7] = 0x10;
+
+        /* Top right */
+        grid[(7 * size) + (size - i - 1)] = 0x10;
+        grid[((size - i - 1) * size) + 7] = 0x10;
+
+        /* Bottom left */
+        grid[(i * size) + (size - 8)] = 0x10;
+        grid[((size - 8) * size) + i] = 0x10;
+
+        /* Bottom right */
+        grid[((size - 8) * size) + (size - i - 1)] = 0x10;
+        grid[((size - i - 1) * size) + (size - 8)] = 0x10;
+    }
+
+    /* Reserve function information region */
+    for (i = 0; i < 9; i++) {
+        /* Top left */
+        grid[(8 * size) + i] = 0x10;
+        grid[(i * size) + 8] = 0x10;
+
+        /* Top right */
+        grid[(8 * size) + (size - i - 1)] = 0x10;
+        grid[((size - i - 1) * size) + 8] = 0x10;
+
+        /* Bottom left */
+        grid[(i * size) + (size - 9)] = 0x10;
+        grid[((size - 9) * size) + i] = 0x10;
+
+        /* Bottom right */
+        grid[((size - 9) * size) + (size - i - 1)] = 0x10;
+        grid[((size - i - 1) * size) + (size - 9)] = 0x10;
+    }
+
+    if (version > 3) {
+        const int k = hx_module_k[version - 1];
+        const int r = hx_module_r[version - 1];
+        const int m = hx_module_m[version - 1];
+        int x, y, row_switch, column_switch;
+        int module_height, module_width;
+        int mod_x, mod_y;
+
+        /* Add assistant alignment patterns to left and right */
+        y = 0;
+        mod_y = 0;
+        do {
+            if (mod_y < m) {
+                module_height = k;
+            } else {
+                module_height = r - 1;
+            }
+
+            if ((mod_y & 1) == 0) {
+                if ((m & 1) == 1) {
+                    hx_plot_assistant(grid, size, 0, y);
+                }
+            } else {
+                if ((m & 1) == 0) {
+                    hx_plot_assistant(grid, size, 0, y);
+                }
+                hx_plot_assistant(grid, size, size - 1, y);
+            }
+
+            mod_y++;
+            y += module_height;
+        } while (y < size);
+
+        /* Add assistant alignment patterns to top and bottom */
+        x = (size - 1);
+        mod_x = 0;
+        do {
+            if (mod_x < m) {
+                module_width = k;
+            } else {
+                module_width = r - 1;
+            }
+
+            if ((mod_x & 1) == 0) {
+                if ((m & 1) == 1) {
+                    hx_plot_assistant(grid, size, x, (size - 1));
+                }
+            } else {
+                if ((m & 1) == 0) {
+                    hx_plot_assistant(grid, size, x, (size - 1));
+                }
+                hx_plot_assistant(grid, size, x, 0);
+            }
+
+            mod_x++;
+            x -= module_width;
+        } while (x >= 0);
+
+        /* Add alignment pattern */
+        column_switch = 1;
+        y = 0;
+        mod_y = 0;
+        do {
+            if (mod_y < m) {
+                module_height = k;
+            } else {
+                module_height = r - 1;
+            }
+
+            if (column_switch == 1) {
+                row_switch = 1;
+                column_switch = 0;
+            } else {
+                row_switch = 0;
+                column_switch = 1;
+            }
+
+            x = (size - 1);
+            mod_x = 0;
+            do {
+                if (mod_x < m) {
+                    module_width = k;
+                } else {
+                    module_width = r - 1;
+                }
+
+                if (row_switch == 1) {
+                    if (!(y == 0 && x == (size - 1))) {
+                        hx_plot_alignment(grid, size, x, y, module_width, module_height);
+                    }
+                    row_switch = 0;
+                } else {
+                    row_switch = 1;
+                }
+                mod_x++;
+                x -= module_width;
+            } while (x >= 0);
+
+            mod_y++;
+            y += module_height;
+        } while (y < size);
+    }
+}
+
+/* Calculate error correction codes */
+static void hx_add_ecc(unsigned char fullstream[], const unsigned char datastream[], const int data_codewords,
+            const int version, const int ecc_level) {
+    unsigned char data_block[180];
+    unsigned char ecc_block[36];
+    int i, j, block;
+    int input_position = 0;
+    int output_position = 0;
+    const int table_d1_pos = ((version - 1) * 36) + ((ecc_level - 1) * 9);
+    rs_t rs;
+
+    rs_init_gf(&rs, 0x163); /* x^8 + x^6 + x^5 + x + 1 = 0 */
+
+    for (i = 0; i < 3; i++) {
+        const int batch_size = hx_table_d1[table_d1_pos + (3 * i)];
+        const int data_length = hx_table_d1[table_d1_pos + (3 * i) + 1];
+        const int ecc_length = hx_table_d1[table_d1_pos + (3 * i) + 2];
+
+        rs_init_code(&rs, ecc_length, 1);
+
+        for (block = 0; block < batch_size; block++) {
+            for (j = 0; j < data_length; j++) {
+                data_block[j] = input_position < data_codewords ? datastream[input_position] : 0;
+                fullstream[output_position++] = data_block[j];
+                input_position++;
+            }
+
+            rs_encode(&rs, data_length, data_block, ecc_block);
+
+            for (j = 0; j < ecc_length; j++) {
+                fullstream[output_position++] = ecc_block[j];
+            }
+        }
+    }
+}
+
+static void hx_set_function_info(unsigned char *grid, const int size, const int version, const int ecc_level,
+            const int bitmask, const int debug_print) {
+    int i, j;
+    char function_information[34];
+    unsigned char fi_cw[3] = {0};
+    unsigned char fi_ecc[4];
+    int bp = 0;
+    rs_t rs;
+
+    /* Form function information string */
+
+    bp = bin_append_posn(version + 20, 8, function_information, bp);
+    bp = bin_append_posn(ecc_level - 1, 2, function_information, bp);
+    bp = bin_append_posn(bitmask, 2, function_information, bp);
+
+    for (i = 0; i < 3; i++) {
+        for (j = 0; j < 4; j++) {
+            if (function_information[(i * 4) + j] == '1') {
+                fi_cw[i] += (0x08 >> j);
+            }
+        }
+    }
+
+    rs_init_gf(&rs, 0x13);
+    rs_init_code(&rs, 4, 1);
+    rs_encode(&rs, 3, fi_cw, fi_ecc);
+
+    for (i = 0; i < 4; i++) {
+        bp = bin_append_posn(fi_ecc[i], 4, function_information, bp);
+    }
+
+    /* Previously added alternating filler pattern here (as does BWIPP) but not mentioned in ISO/IEC 20830:2021 and
+       does not appear in Figure 1 nor in the figures in Annex K (although does appear in Figure 2 and Figures 4-9)
+       nor in the AIM ITS/04-023:2022 examples: so just clear */
+    for (i = 28; i < 34; i++) {
+        function_information[i] = '0';
+    }
+
+    if (debug_print) {
+        printf("Version: %d, ECC: %d, Mask: %d, Structural Info: %.34s\n", version, ecc_level, bitmask,
+                function_information);
+    }
+
+    /* Add function information to symbol */
+    for (i = 0; i < 9; i++) {
+        if (function_information[i] == '1') {
+            grid[(8 * size) + i] = 0x01;
+            grid[((size - 8 - 1) * size) + (size - i - 1)] = 0x01;
+        }
+        if (function_information[i + 8] == '1') {
+            grid[((8 - i) * size) + 8] = 0x01;
+            grid[((size - 8 - 1 + i) * size) + (size - 8 - 1)] = 0x01;
+        }
+        if (function_information[i + 17] == '1') {
+            grid[(i * size) + (size - 1 - 8)] = 0x01;
+            grid[((size - 1 - i) * size) + 8] = 0x01;
+        }
+        if (function_information[i + 25] == '1') {
+            grid[(8 * size) + (size - 1 - 8 + i)] = 0x01;
+            grid[((size - 1 - 8) * size) + (8 - i)] = 0x01;
+        }
+    }
+}
+
+/* Rearrange data in batches of 13 codewords (section 5.8.2) */
+static void hx_make_picket_fence(const unsigned char fullstream[], unsigned char picket_fence[],
+            const int streamsize) {
+    int i, start;
+    int output_position = 0;
+
+    for (start = 0; start < 13; start++) {
+        for (i = start; i < streamsize; i += 13) {
+            picket_fence[output_position] = fullstream[i];
+            output_position++;
+        }
+    }
+}
+
+/* Evaluate a bitmask according to table 9 */
+static int hx_evaluate(const unsigned char *local, const int size) {
+    static const unsigned char h1010111[7] = { 1, 0, 1, 0, 1, 1, 1 };
+    static const unsigned char h1110101[7] = { 1, 1, 1, 0, 1, 0, 1 };
+
+    int x, y, r, block;
+    int result = 0;
+    int state;
+    int a, b, afterCount, beforeCount;
+
+    /* Test 1: 1:1:1:1:3 or 3:1:1:1:1 ratio pattern in row/column */
+    /* Vertical */
+    for (x = 0; x < size; x++) {
+        for (y = 0; y <= (size - 7); y++) {
+            if (local[y * size + x] && local[(y + 1) * size + x] != local[(y + 5) * size + x] &&
+                    local[(y + 2) * size + x] && !local[(y + 3) * size + x] &&
+                    local[(y + 4) * size + x] && local[(y + 6) * size + x]) {
+                /* Pattern found, check before and after */
+                beforeCount = 0;
+                for (b = (y - 1); b >= (y - 3); b--) {
+                    if (b < 0) { /* Count < edge as whitespace */
+                        beforeCount = 3;
+                        break;
+                    }
+                    if (local[(b * size) + x]) {
+                        break;
+                    }
+                    beforeCount++;
+                }
+                if (beforeCount == 3) {
+                    /* Pattern is preceded by light area 3 modules wide */
+                    result += 50;
+                } else {
+                    afterCount = 0;
+                    for (a = (y + 7); a <= (y + 9); a++) {
+                        if (a >= size) { /* Count > edge as whitespace */
+                            afterCount = 3;
+                            break;
+                        }
+                        if (local[(a * size) + x]) {
+                            break;
+                        }
+                        afterCount++;
+                    }
+                    if (afterCount == 3) {
+                        /* Pattern is followed by light area 3 modules wide */
+                        result += 50;
+                    }
+                }
+                y++; /* Skip to next possible match */
+            }
+        }
+    }
+
+    /* Horizontal */
+    for (y = 0; y < size; y++) {
+        r = y * size;
+        for (x = 0; x <= (size - 7); x++) {
+            if (memcmp(local + r + x, h1010111, 7) == 0 || memcmp(local + r + x, h1110101, 7) == 0) {
+                /* Pattern found, check before and after */
+                beforeCount = 0;
+                for (b = (x - 1); b >= (x - 3); b--) {
+                    if (b < 0) { /* Count < edge as whitespace */
+                        beforeCount = 3;
+                        break;
+                    }
+                    if (local[r + b]) {
+                        break;
+                    }
+                    beforeCount++;
+                }
+
+                if (beforeCount == 3) {
+                    /* Pattern is preceded by light area 3 modules wide */
+                    result += 50;
+                } else {
+                    afterCount = 0;
+                    for (a = (x + 7); a <= (x + 9); a++) {
+                        if (a >= size) { /* Count > edge as whitespace */
+                            afterCount = 3;
+                            break;
+                        }
+                        if (local[r + a]) {
+                            break;
+                        }
+                        afterCount++;
+                    }
+                    if (afterCount == 3) {
+                        /* Pattern is followed by light area 3 modules wide */
+                        result += 50;
+                    }
+                }
+                x++; /* Skip to next possible match */
+            }
+        }
+    }
+
+    /* Test 2: Adjacent modules in row/column in same colour */
+    /* In AIMD-15 section 5.8.3.2 it is stated... “In Table 9 below, i refers to the row
+     * position of the module.” - however i being the length of the run of the
+     * same colour (i.e. "block" below) in the same fashion as ISO/IEC 18004
+     * makes more sense. -- Confirmed by Wang Yi */
+    /* Fixed in ISO/IEC 20830 section 5.8.4.3 "In Table, i refers to the modules with
+       same color." */
+
+    /* Vertical */
+    for (x = 0; x < size; x++) {
+        block = 0;
+        state = 0;
+        for (y = 0; y < size; y++) {
+            if (local[(y * size) + x] == state) {
+                block++;
+            } else {
+                if (block >= 3) {
+                    result += block * 4;
+                }
+                block = 1;
+                state = local[(y * size) + x];
+            }
+        }
+        if (block >= 3) {
+            result += block * 4;
+        }
+    }
+
+    /* Horizontal */
+    for (y = 0; y < size; y++) {
+        r = y * size;
+        block = 0;
+        state = 0;
+        for (x = 0; x < size; x++) {
+            if (local[r + x] == state) {
+                block++;
+            } else {
+                if (block >= 3) {
+                    result += block * 4;
+                }
+                block = 1;
+                state = local[r + x];
+            }
+        }
+        if (block >= 3) {
+            result += block * 4;
+        }
+    }
+
+    return result;
+}
+
+/* Apply the four possible bitmasks for evaluation */
+/* TODO: Haven't been able to replicate (or even get close to) the penalty scores in ISO/IEC 20830:2021
+ * Annex K examples */
+static void hx_apply_bitmask(unsigned char *grid, const int size, const int version, const int ecc_level,
+            const int user_mask, const int debug_print) {
+    int x, y;
+    int i, j, r, k;
+    int pattern, penalty[4] = {0};
+    int best_pattern;
+    int bit;
+    const int size_squared = size * size;
+    unsigned char *mask = (unsigned char *) z_alloca(size_squared);
+    unsigned char *local = (unsigned char *) z_alloca(size_squared);
+
+    /* Perform data masking */
+    memset(mask, 0, size_squared);
+    for (y = 0; y < size; y++) {
+        r = y * size;
+        for (x = 0; x < size; x++) {
+            k = r + x;
+
+            if (!(grid[k] & 0xf0)) {
+                j = x + 1;
+                i = y + 1;
+                if (((i + j) & 1) == 0) {
+                    mask[k] |= 0x02;
+                }
+                if (((((i + j) % 3) + (j % 3)) & 1) == 0) {
+                    mask[k] |= 0x04;
+                }
+                if ((((i % j) + (j % i) + (i % 3) + (j % 3)) & 1) == 0) {
+                    mask[k] |= 0x08;
+                }
+            }
+        }
+    }
+
+    if (user_mask) {
+        best_pattern = user_mask - 1;
+    } else {
+        /* apply data masks to grid, result in local */
+
+        /* Do null pattern 00 separately first */
+        pattern = 0;
+        for (k = 0; k < size_squared; k++) {
+            local[k] = grid[k] & 0x0f;
+        }
+        /* Set the Structural Info */
+        hx_set_function_info(local, size, version, ecc_level, pattern, 0 /*debug_print*/);
+
+        /* Evaluate result */
+        penalty[pattern] = hx_evaluate(local, size);
+
+        best_pattern = 0;
+        for (pattern = 1; pattern < 4; pattern++) {
+            bit = 1 << pattern;
+            for (k = 0; k < size_squared; k++) {
+                if (mask[k] & bit) {
+                    local[k] = grid[k] ^ 0x01;
+                } else {
+                    local[k] = grid[k] & 0x0f;
+                }
+            }
+            /* Set the Structural Info */
+            hx_set_function_info(local, size, version, ecc_level, pattern, 0 /*debug_print*/);
+
+            /* Evaluate result */
+            penalty[pattern] = hx_evaluate(local, size);
+            if (penalty[pattern] < penalty[best_pattern]) {
+                best_pattern = pattern;
+            }
+        }
+    }
+
+    if (debug_print) {
+        printf("Mask: %d (%s)", best_pattern, user_mask ? "specified" : "automatic");
+        if (!user_mask) {
+            for (pattern = 0; pattern < 4; pattern++) printf(" %d:%d", pattern, penalty[pattern]);
+        }
+        fputc('\n', stdout);
+    }
+
+    /* Apply mask */
+    if (best_pattern) { /* If not null mask */
+        if (!user_mask && best_pattern == 3) { /* Reuse last */
+            memcpy(grid, local, size_squared);
+        } else {
+            bit = 1 << best_pattern;
+            for (k = 0; k < size_squared; k++) {
+                if (mask[k] & bit) {
+                    grid[k] ^= 0x01;
+                }
+            }
+        }
+    }
+    /* Set the Structural Info */
+    hx_set_function_info(grid, size, version, ecc_level, best_pattern, debug_print);
+}
+
+/* Han Xin Code - main */
+INTERNAL int hanxin(struct zint_symbol *symbol, struct zint_seg segs[], const int seg_count) {
+    int warn_number = 0;
+    int est_binlen;
+    int ecc_level = symbol->option_1;
+    int i, j, j_max, version;
+    int full_multibyte;
+    int user_mask;
+    int data_codewords = 0, size;
+    int size_squared;
+    int codewords;
+    int bin_len;
+    const int debug_print = symbol->debug & ZINT_DEBUG_PRINT;
+    const int eci_length_segs = get_eci_length_segs(segs, seg_count);
+    struct zint_seg *local_segs = (struct zint_seg *) z_alloca(sizeof(struct zint_seg) * seg_count);
+    unsigned int *ddata = (unsigned int *) z_alloca(sizeof(unsigned int) * eci_length_segs);
+    char *mode = (char *) z_alloca(eci_length_segs);
+    char *binary;
+    unsigned char *datastream;
+    unsigned char *fullstream;
+    unsigned char *picket_fence;
+    unsigned char *grid;
+
+    segs_cpy(symbol, segs, seg_count, local_segs); /* Shallow copy (needed to set default ECI & protect lengths) */
+
+    /* If ZINT_FULL_MULTIBYTE set use Hanzi mode in DATA_MODE or for non-GB 18030 in UNICODE_MODE */
+    full_multibyte = (symbol->option_3 & 0xFF) == ZINT_FULL_MULTIBYTE;
+    user_mask = (symbol->option_3 >> 8) & 0x0F; /* User mask is pattern + 1, so >= 1 and <= 4 */
+    if (user_mask > 4) {
+        user_mask = 0; /* Ignore */
+    }
+
+    if ((symbol->input_mode & 0x07) == DATA_MODE) {
+        gb18030_cpy_segs(local_segs, seg_count, ddata, full_multibyte);
+    } else {
+        unsigned int *dd = ddata;
+        for (i = 0; i < seg_count; i++) {
+            int done = 0;
+            if (local_segs[i].eci != 32 || seg_count > 1) { /* Unless ECI 32 (GB 18030) or have multiple segments */
+                /* Try other conversions (ECI 0 defaults to ISO/IEC 8859-1) */
+                int error_number = gb18030_utf8_to_eci(local_segs[i].eci, local_segs[i].source, &local_segs[i].length,
+                                                        dd, full_multibyte);
+                if (error_number == 0) {
+                    done = 1;
+                } else if (local_segs[i].eci || seg_count > 1) {
+                    return errtxtf(error_number, symbol, 545, "Invalid character in input for ECI '%d'",
+                                    local_segs[i].eci);
+                }
+            }
+            if (!done) {
+                /* Try GB 18030 */
+                int error_number = gb18030_utf8(symbol, local_segs[i].source, &local_segs[i].length, dd);
+                if (error_number != 0) {
+                    return error_number;
+                }
+                if (local_segs[i].eci != 32) {
+                    warn_number = errtxt(ZINT_WARN_NONCOMPLIANT, symbol, 543,
+                                            "Converted to GB 18030 but no ECI specified");
+                }
+            }
+            dd += local_segs[i].length;
+        }
+    }
+
+    hx_define_mode_segs(mode, ddata, local_segs, seg_count, debug_print);
+
+    est_binlen = hx_calc_binlen_segs(mode, ddata, local_segs, seg_count);
+    if (debug_print) {
+        printf("Estimated binary length: %d\n", est_binlen);
+    }
+
+    binary = (char *) malloc(est_binlen + 1);
+
+    if ((ecc_level <= 0) || (ecc_level >= 5)) {
+        ecc_level = 1;
+    }
+
+    hx_calculate_binary_segs(binary, mode, ddata, local_segs, seg_count, &bin_len, debug_print);
+    codewords = bin_len >> 3;
+    if (bin_len & 0x07) {
+        codewords++;
+    }
+    if (debug_print) {
+        printf("Num. of codewords: %d (%d padbits)\n", codewords, bin_len & 0x07);
+    }
+
+    version = 85;
+    for (i = 84; i > 0; i--) {
+        if (hx_data_codewords[ecc_level - 1][i - 1] >= codewords) {
+            version = i;
+            data_codewords = hx_data_codewords[ecc_level - 1][i - 1];
+        }
+    }
+
+    if (version == 85) {
+        free(binary);
+        return errtxtf(ZINT_ERROR_TOO_LONG, symbol, 541, "Input too long, requires %d codewords (maximum 3264)",
+                        codewords);
+    }
+
+    if ((symbol->option_2 < 0) || (symbol->option_2 > 84)) {
+        symbol->option_2 = 0;
+    }
+
+    if (symbol->option_2 > version) {
+        version = symbol->option_2;
+    }
+
+    if ((symbol->option_2 != 0) && (symbol->option_2 < version)) {
+        free(binary);
+        if (ecc_level == 1) {
+            return errtxtf(ZINT_ERROR_TOO_LONG, symbol, 542,
+                            "Input too long for Version %1$d, requires %2$d codewords (maximum %3$d)",
+                            symbol->option_2, codewords, hx_data_codewords[ecc_level - 1][symbol->option_2 - 1]);
+        }
+        return errtxtf(ZINT_ERROR_TOO_LONG, symbol, 542,
+                        "Input too long for Version %1$d, ECC %2$d, requires %3$d codewords (maximum %4$d)",
+                        symbol->option_2, ecc_level, codewords,
+                        hx_data_codewords[ecc_level - 1][symbol->option_2 - 1]);
+    }
+
+    /* If there is spare capacity, increase the level of ECC */
+
+    /* Unless explicitly specified (within min/max bounds) by user */
+    if (symbol->option_1 == -1 || symbol->option_1 != ecc_level) {
+        if (ecc_level == 1 && codewords <= hx_data_codewords[1][version - 1]) {
+            ecc_level = 2;
+            data_codewords = hx_data_codewords[1][version - 1];
+        }
+
+        if (ecc_level == 2 && codewords <= hx_data_codewords[2][version - 1]) {
+            ecc_level = 3;
+            data_codewords = hx_data_codewords[2][version - 1];
+        }
+
+        if (ecc_level == 3 && codewords <= hx_data_codewords[3][version - 1]) {
+            ecc_level = 4;
+            data_codewords = hx_data_codewords[3][version - 1];
+        }
+    }
+
+    size = (version * 2) + 21;
+    size_squared = size * size;
+
+    datastream = (unsigned char *) z_alloca(data_codewords);
+    fullstream = (unsigned char *) z_alloca(hx_total_codewords[version - 1]);
+    picket_fence = (unsigned char *) z_alloca(hx_total_codewords[version - 1]);
+    grid = (unsigned char *) z_alloca(size_squared);
+
+    memset(datastream, 0, data_codewords);
+
+    for (i = 0; i < bin_len; i++) {
+        if (binary[i] == '1') {
+            datastream[i >> 3] |= 0x80 >> (i & 0x07);
+        }
+    }
+    free(binary);
+
+    if (debug_print) {
+        printf("Datastream (%d):", data_codewords);
+        for (i = 0; i < data_codewords; i++) {
+            printf(" %.2x", datastream[i]);
+        }
+        fputc('\n', stdout);
+    }
+#ifdef ZINT_TEST
+    if (symbol->debug & ZINT_DEBUG_TEST) debug_test_codeword_dump(symbol, datastream, data_codewords);
+#endif
+
+    hx_setup_grid(grid, size, version);
+
+    hx_add_ecc(fullstream, datastream, data_codewords, version, ecc_level);
+
+    if (debug_print) {
+        printf("Fullstream (%d):", hx_total_codewords[version - 1]);
+        for (i = 0; i < hx_total_codewords[version - 1]; i++) {
+            printf(" %.2x", fullstream[i]);
+        }
+        fputc('\n', stdout);
+    }
+
+    hx_make_picket_fence(fullstream, picket_fence, hx_total_codewords[version - 1]);
+
+    /* Populate grid */
+    j = 0;
+    j_max = hx_total_codewords[version - 1] * 8;
+    for (i = 0; i < size_squared; i++) {
+        if (grid[i] == 0x00) {
+            if (j < j_max) {
+                if (picket_fence[(j >> 3)] & (0x80 >> (j & 0x07))) {
+                    grid[i] = 0x01;
+                }
+                j++;
+            } else {
+                break;
+            }
+        }
+    }
+
+    hx_apply_bitmask(grid, size, version, ecc_level, user_mask, debug_print);
+
+    symbol->width = size;
+    symbol->rows = size;
+
+    for (i = 0; i < size; i++) {
+        const int r = i * size;
+        for (j = 0; j < size; j++) {
+            if (grid[r + j] & 0x01) {
+                set_module(symbol, i, j);
+            }
+        }
+        symbol->row_height[i] = 1;
+    }
+    symbol->height = size;
+
+    return warn_number;
+}
+
+/* vim: set ts=4 sw=4 et : */