<?php
/* Generate ECI single-byte tables & routines from unicode.org mapping files */
/*
    libzueci - an open source UTF-8 ECI library adapted from libzint
    Copyright (C) 2022 gitlost
*/
/* SPDX-License-Identifier: BSD-3-Clause */

/*
 * To create "zueci_sb.h" (from project root directory):
 *
 *   php tools/gen_zueci_sb_h.php
 *
 * Requires "8859-*.TXT" from https://unicode.org/Public/MAPPINGS/ISO8859/
 * and "CP437.TXT" from https://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/PC/
 * and "CP1250/1/2/6.TXT" from https://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/
 * to be in "tools/data" directory.
 */

$basename = basename(__FILE__);
$dirname = dirname(__FILE__);

$opts = getopt('d:o:');
$data_dirname = isset($opts['d']) ? $opts['d'] : ($dirname . '/data'); // Where to load file from.
$out_dirname = isset($opts['o']) ? $opts['o'] : ($dirname . '/..'); // Where to put output.

$out = array();

$head = <<<'EOD'
/*  zueci_sb.h - Extended Channel Interpretations single-byte, generated by "tools/gen_zueci_sb_h.php" from
    "https://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/PC/CP437.TXT" and
    "https://unicode.org/Public/MAPPINGS/ISO8859/8859-*.TXT" and
    "https://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP125*.TXT" */
/*
    libzueci - an open source UTF-8 ECI library adapted from libzint
    Copyright (C) 2022 gitlost
 */
/* SPDX-License-Identifier: BSD-3-Clause */

#ifndef ZUECI_SB_H
#define ZUECI_SB_H
EOD;

$out = explode("\n", $head);

// Read the CP437 file

$tot_cp437 = 0;

//$file = $data_dirname . '/' . 'CP437.TXT';
$file = 'https://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/PC/CP437.TXT';

if (($get = file_get_contents($file)) === false) {
    error_log($error = "$basename: ERROR: Could not read mapping file \"$file\"");
    exit($error . PHP_EOL);
}

$lines = explode("\n", $get);

// Parse the file.

$sort = array();
$sb = array();
foreach ($lines as $line) {
    $line = trim($line);
    if ($line === '' || strncmp($line, '0x', 2) !== 0 || strpos($line, "*** NO MAPPING ***") !== false) {
        continue;
    }
    $matches = array();
    if (preg_match('/^0x([0-9a-f]{2})[ \t]+0x([0-9a-f]{4})[ \t].*$/', $line, $matches)) {
        $mb = hexdec($matches[1]);
        $unicode = hexdec($matches[2]);
        if ($unicode >= 0x80) {
            $sort[] = $unicode;
            $sb[] = $mb;
        }
    }
}

array_multisort($sort, $sb);

// Output.

$out[] = '';
$out[] = '/* Tables for ECIs 0 & 2 IBM CP437 */';
$cnt = count($sort);
$out[] = 'static const zueci_u16 zueci_cp437_u_u[' . $cnt . '] = { /* Unicode codepoints sorted */';
$line = '   ';
for ($i = 0; $i < $cnt; $i++) {
    if ($i && $i % 8 === 0) {
        $out[] = $line;
        $line = '   ';
    }
    $line .= sprintf(' 0x%04X,', $sort[$i]);
}
if ($line !== '   ') {
    $out[] = $line;
}
$out[] = '};';
$tot_cp437 += $cnt * 2;

$cnt = count($sb);
$out[] = '#ifndef ZUECI_EMBED_NO_TO_ECI';
$out[] = 'static const unsigned char zueci_cp437_u_sb[' . $cnt . '] = { /* Single-byte in Unicode order */';
$line = ' ';
for ($i = 0; $i < $cnt; $i++) {
    if ($i && $i % 8 === 0) {
        $out[] = $line;
        $line = ' ';
    }
    $line .= sprintf('   0x%02X,', $sb[$i]);
}
if ($line !== '   ') {
    $out[] = $line;
}
$out[] = '};';
$tot_cp437 += $cnt;

$u_sb = array_flip($sb);
$b = 0x80;
$cnt = 256 - $b;
$max_idx = -1;
for ($i = 0; $i < $cnt; $i++) {
    if (isset($u_sb[$i + $b])) {
        $max_idx = $i;
    }
}
$cnt = $max_idx + 1;
$out[] = '#endif /* ZUECI_EMBED_NO_TO_ECI */';
$out[] = '#ifndef ZUECI_EMBED_NO_TO_UTF8';
$out[] = 'static const char zueci_cp437_sb_u[' . $cnt . '] = { /* Single-byte sorted indexes into Unicode table */';
$line = ' ';
for ($i = 0; $i < $cnt; $i++) {
    if ($i && $i % 8 === 0) {
        $out[] = $line;
        $line = ' ';
    }
    $line .= sprintf('    % 3d,', isset($u_sb[$i + $b]) ? $u_sb[$i + $b] : -1);
}
if ($line !== '   ') {
    $out[] = $line;
}
$out[] = '};';
$out[] = '#endif /* ZUECI_EMBED_NO_TO_UTF8 */';
$tot_cp437 += $cnt;

if (0) {
    $out[] = '';
    $out[] = '/* Total CP437 bytes: ' . $tot_cp437 . ' */';
}

$u_iso8859 = <<<'EOD'

#ifndef ZUECI_EMBED_NO_TO_ECI
/* Forward reference to base Unicode to ISO/IEC 8859 routine - see "zueci.c" */
static int zueci_u_iso8859(const zueci_u32 u, const zueci_u16 *tab_s, const zueci_u16 *tab_u_u,
            const unsigned char *tab_u_sb, int e, unsigned char *dest);
#endif
#ifndef ZUECI_EMBED_NO_TO_UTF8
/* Forward reference to base ISO/IEC 8859 to Unicode routine - see "zueci.c" */
static int zueci_iso8859_u(const unsigned char c, const unsigned int flags, const zueci_u16 *tab_s,
            const zueci_u16 *tab_u_u, const char *tab_sb_u, const int c2_max, zueci_u32 *p_u);
#endif
EOD;

$out = array_merge($out, explode("\n", $u_iso8859));

$iso8859_comments = array(
    array(), array(), // 0-1
    //    ECI    Description
    array( '4', 'Latin alphabet No. 2 (Latin-2)' ),
    array( '5', 'Latin alphabet No. 3 (Latin-3) (South European)' ),
    array( '6', 'Latin alphabet No. 4 (Latin-4) (North European)' ),
    array( '7', 'Latin/Cyrillic' ),
    array( '8', 'Latin/Arabic' ),
    array( '9', 'Latin/Greek' ),
    array( '10', 'Latin/Hebrew' ),
    array( '11', 'Latin alphabet No. 5 (Latin-5) (Latin/Turkish)' ),
    array( '12', 'Latin alphabet No. 6 (Latin-6) (Nordic)' ),
    array( '13', 'Latin/Thai' ),
    array(),
    array( '15', 'Latin alphabet No. 7 (Latin-7) (Baltic Rim)' ),
    array( '16', 'Latin alphabet No. 8 (Latin-8) (Celtic)' ),
    array( '17', 'Latin alphabet No. 9 (Latin-9)' ),
    array( '18', 'Latin alphabet No. 10 (Latin-10) (South-Eastern European)' ),
);

// Read the 8859 files.

$tot_8859 = 0;
for ($k = 2; $k <= 16; $k++) {
    if ($k == 12) continue;

    //$file = $data_dirname . '/' . '8859-' . $k . '.TXT';
    $file = 'https://unicode.org/Public/MAPPINGS/ISO8859/' . '8859-' . $k . '.TXT';

    if (($get = file_get_contents($file)) === false) {
        error_log($error = "$basename: ERROR: Could not read mapping file \"$file\"");
        exit($error . PHP_EOL);
    }

    $lines = explode("\n", $get);

    // Parse the file.

    $sort = array();
    $sb = array();
    $same = array();
    foreach ($lines as $line) {
        $line = trim($line);
        if ($line === '' || strncmp($line, '0x', 2) !== 0 || strpos($line, "*** NO MAPPING ***") !== false) {
            continue;
        }
        $matches = array();
        if (preg_match('/^0x([0-9A-F]{2})[ \t]+0x([0-9A-F]{4})[ \t].*$/', $line, $matches)) {
            $mb = hexdec($matches[1]);
            $unicode = hexdec($matches[2]);
            if ($unicode >= 0xA0) {
                if ($unicode <= 0xFF && $unicode == $mb) {
                    $same[] = $mb;
                } else {
                    $sort[] = $unicode;
                    $sb[] = $mb;
                }
            }
        }
    }

    sort($same);
    array_multisort($sort, $sb);

    $s = array( 0, 0, 0, 0, 0, 0 );
    for ($i = 0, $cnt = count($same); $i < $cnt; $i++) {
        $v = $same[$i] - 0xA0;
        $j = $v >> 4;
        $s[$j] |= 1 << ($v & 0xF);
    }

    // Output.

    $out[] = '';
    $out[] = '/* Tables for ECI ' . $iso8859_comments[$k][0] . ' ISO/IEC 8859-' . $k . ' */';
    $out[] = 'static const zueci_u16 zueci_iso8859_' . $k . '_s[6] = { /* Straight-thru bit-flags */';
    $line = '   ';
    for ($i = 0; $i < 6; $i++) {
        $line .= sprintf(" 0x%04X,", $s[$i]);
    }
    $out[] = $line;
    $out[] = '};';
    $tot_8859 += 6 * 2;

    $cnt = count($sort);
    $out[] = 'static const zueci_u16 zueci_iso8859_' . $k . '_u_u[' . $cnt . '] = { /* Unicode codepoints sorted */';
    $line = '   ';
    for ($i = 0; $i < $cnt; $i++) {
        if ($i && $i % 8 === 0) {
            $out[] = $line;
            $line = '   ';
        }
        $line .= sprintf(' 0x%04X,', $sort[$i]);
    }
    if ($line !== '   ') {
        $out[] = $line;
    }
    $out[] = '};';
    $tot_8859 += $cnt * 2;

    $cnt = count($sb);
    $out[] = '#ifndef ZUECI_EMBED_NO_TO_ECI';
    $out[] = 'static const unsigned char zueci_iso8859_' . $k . '_u_sb[' . $cnt . '] = { /* Single-byte in Unicode order */';
    $line = ' ';
    for ($i = 0; $i < $cnt; $i++) {
        if ($i && $i % 8 === 0) {
            $out[] = $line;
            $line = ' ';
        }
        $line .= sprintf('   0x%02X,', $sb[$i]);
    }
    if ($line !== '   ') {
        $out[] = $line;
    }
    $out[] = '};';
    $out[] = '#endif';
    $tot_8859 += $cnt;

    $u_sb = array_flip($sb);
    $b = 0xA0;
    $cnt = 256 - $b;
    $max_idx = -1;
    for ($i = 0; $i < $cnt; $i++) {
        if (isset($u_sb[$i + $b])) {
            $max_idx = $i;
        }
    }
    $cnt = $max_idx + 1;
    $out[] = '#ifndef ZUECI_EMBED_NO_TO_UTF8';
    $out[] = 'static const char zueci_iso8859_' . $k . '_sb_u[' . $cnt . '] = { /* Single-byte sorted indexes into Unicode table */';
    $line = ' ';
    for ($i = 0; $i < $cnt; $i++) {
        if ($i && $i % 8 === 0) {
            $out[] = $line;
            $line = ' ';
        }
        $line .= sprintf('     % 2d,', isset($u_sb[$i + $b]) ? $u_sb[$i + $b] : -1);
    }
    if ($line !== '   ') {
        $out[] = $line;
    }
    $out[] = '};';
    $out[] = '#endif';
    $tot_8859 += $cnt;

    $out[] = '';
    $out[] = '#ifndef ZUECI_EMBED_NO_TO_ECI';
    $out[] = '/* ECI ' . $iso8859_comments[$k][0] . ' ISO/IEC 8859-' . $k . ' ' . $iso8859_comments[$k][1] . ' */';
    $out[] = 'static int zueci_u_iso8859_' . $k . '(const zueci_u32 u, unsigned char *dest) {';
    $out[] = '    return zueci_u_iso8859(u, zueci_iso8859_' . $k . '_s, zueci_iso8859_' . $k . '_u_u, zueci_iso8859_' . $k . '_u_sb,';
    $out[] = '                           ZUECI_ASIZE(zueci_iso8859_' . $k . '_u_u) - 1, dest);';
    $out[] = '}';
    $out[] = '#endif';
    $out[] = '#ifndef ZUECI_EMBED_NO_TO_UTF8';
    $out[] = 'static int zueci_iso8859_' . $k . '_u(const unsigned char *src, const zueci_u32 len, const unsigned int flags,';
    $out[] = '            zueci_u32 *p_u) {';
    $out[] = '    (void)len;';
    $out[] = '    return zueci_iso8859_u(*src, flags, zueci_iso8859_' . $k . '_s, zueci_iso8859_' . $k . '_u_u, zueci_iso8859_' . $k . '_sb_u,';
    $out[] = '                           ZUECI_ASIZE(zueci_iso8859_' . $k . '_sb_u), p_u);';
    $out[] = '}';
    $out[] = '#endif';
}

if (0) {
    $out[] = '';
    $out[] = '/* Total ISO/IEC 8859 bytes: ' . $tot_8859 . ' */';
}

$u_cp125x = <<<'EOD'

#ifndef ZUECI_EMBED_NO_TO_ECI
/* Forward reference to base Unicode to Windows-125x routine - see "zueci.c" */
static int zueci_u_cp125x(const zueci_u32 u, const zueci_u16 *tab_s, const zueci_u16 *tab_u_u,
            const unsigned char *tab_u_sb, int e, unsigned char *dest);
#endif

#ifndef ZUECI_EMBED_NO_TO_UTF8
/* Forward reference to base Windows-125x to Unicode routine - see "zueci.c" */
static int zueci_cp125x_u(const unsigned char c, const unsigned int flags, const zueci_u16 *tab_s,
            const zueci_u16 *tab_u_u, const char *tab_sb_u, const int c_max, zueci_u32 *p_u);
#endif
EOD;

$out = array_merge($out, explode("\n", $u_cp125x));

$cp125x_comments = array(
    //    ECI    Description
    array( '21', 'Latin 2 (Central Europe)' ),
    array( '22', 'Cyrillic' ),
    array( '23', 'Latin 1' ),
    array(), array(), array(),
    array( '24', 'Arabic' ),
);

// Read the Windows 125x files.

$tot_cp125x = 0;
for ($k = 0; $k <= 6; $k++) {
    if ($k >= 3 && $k <= 5) continue;

    //$file = $data_dirname . '/' . 'CP125' . $k . '.TXT';
    $file = 'https://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/' . 'CP125' . $k . '.TXT';

    if (($get = file_get_contents($file)) === false) {
        error_log($error = "$basename: ERROR: Could not read mapping file \"$file\"");
        exit($error . PHP_EOL);
    }

    $lines = explode("\n", $get);

    // Parse the file.

    $sort = array();
    $sb = array();
    $same = array();
    foreach ($lines as $line) {
        $line = trim($line);
        if ($line === '' || strncmp($line, '0x', 2) !== 0 || strpos($line, "*** NO MAPPING ***") !== false) {
            continue;
        }
        $matches = array();
        if (preg_match('/^0x([0-9A-F]{2})[ \t]+0x([0-9A-F]{4})[ \t].*$/', $line, $matches)) {
            $mb = hexdec($matches[1]);
            $unicode = hexdec($matches[2]);
            if ($unicode >= 0x80) {
                if ($unicode <= 0xFF && $unicode == $mb) {
                    $same[] = $mb;
                } else {
                    $sort[] = $unicode;
                    $sb[] = $mb;
                }
            }
        }
    }

    sort($same);
    array_multisort($sort, $sb);

    $s = array( 0, 0, 0, 0, 0, 0 );
    for ($i = 0, $cnt = count($same); $i < $cnt; $i++) {
        $v = $same[$i] - 0xA0;
        $j = $v >> 4;
        $s[$j] |= 1 << ($v & 0xF);
    }

    // Output.

    $out[] = '';
    $out[] = '/* Tables for ECI ' . $cp125x_comments[$k][0] . ' Windows 125' . $k . ' */';
    $out[] = 'static const zueci_u16 zueci_cp125' . $k . '_s[6] = { /* Straight-thru bit-flags */';
    $line = '   ';
    for ($i = 0; $i < 6; $i++) {
        $line .= sprintf(" 0x%04X,", $s[$i]);
    }
    $out[] = $line;
    $out[] = '};';
    $tot_cp125x += 6 * 2;

    $cnt = count($sort);
    $out[] = 'static const zueci_u16 zueci_cp125' . $k . '_u_u[' . $cnt . '] = { /* Unicode codepoints sorted */';
    $line = '   ';
    for ($i = 0; $i < $cnt; $i++) {
        if ($i && $i % 8 === 0) {
            $out[] = $line;
            $line = '   ';
        }
        $line .= sprintf(' 0x%04X,', $sort[$i]);
    }
    if ($line !== '   ') {
        $out[] = $line;
    }
    $out[] = '};';
    $tot_cp125x += $cnt * 2;

    $cnt = count($sb);
    $out[] = '#ifndef ZUECI_EMBED_NO_TO_ECI';
    $out[] = 'static const unsigned char zueci_cp125' . $k . '_u_sb[' . $cnt . '] = { /* Single-byte in Unicode order */';
    $line = ' ';
    for ($i = 0; $i < $cnt; $i++) {
        if ($i && $i % 8 === 0) {
            $out[] = $line;
            $line = ' ';
        }
        $line .= sprintf('   0x%02X,', $sb[$i]);
    }
    if ($line !== '   ') {
        $out[] = $line;
    }
    $out[] = '};';
    $out[] = '#endif /* ZUECI_EMBED_NO_TO_ECI */';
    $tot_cp125x += $cnt;

    $u_sb = array_flip($sb);
    $b = 0x80;
    $cnt = 256 - $b;
    $max_idx = -1;
    for ($i = 0; $i < $cnt; $i++) {
        if (isset($u_sb[$i + $b])) {
            $max_idx = $i;
        }
    }
    $cnt = $max_idx + 1;
    $out[] = '#ifndef ZUECI_EMBED_NO_TO_UTF8';
    $out[] = 'static const char zueci_cp125' . $k . '_sb_u[' . $cnt . '] = { /* Single-byte sorted indexes into Unicode table */';
    $line = ' ';
    for ($i = 0; $i < $cnt; $i++) {
        if ($i && $i % 8 === 0) {
            $out[] = $line;
            $line = ' ';
        }
        $line .= sprintf('    % 3d,', isset($u_sb[$i + $b]) ? $u_sb[$i + $b] : -1);
    }
    if ($line !== '   ') {
        $out[] = $line;
    }
    $out[] = '};';
    $out[] = '#endif /* ZUECI_EMBED_NO_TO_UTF8 */';
    $tot_cp125x += $cnt;

    $out[] = '';
    $out[] = '#ifndef ZUECI_EMBED_NO_TO_ECI';
    $out[] = '/* ECI ' . $cp125x_comments[$k][0] . ' Windows-125' . $k . ' ' . $cp125x_comments[$k][1] . ' */';
    $out[] = 'static int zueci_u_cp125' . $k . '(const zueci_u32 u, unsigned char *dest) {';
    $out[] = '    return zueci_u_cp125x(u, zueci_cp125' . $k . '_s, zueci_cp125' . $k . '_u_u, zueci_cp125' . $k . '_u_sb,';
    $out[] = '                          ZUECI_ASIZE(zueci_cp125' . $k . '_u_u) - 1, dest);';
    $out[] = '}';
    $out[] = '#endif';
    $out[] = '#ifndef ZUECI_EMBED_NO_TO_UTF8';
    $out[] = 'static int zueci_cp125' . $k . '_u(const unsigned char *src, const zueci_u32 len, const unsigned int flags, zueci_u32 *p_u) {';
    $out[] = '    (void)len;';
    $out[] = '    return zueci_cp125x_u(*src, flags, zueci_cp125' . $k . '_s, zueci_cp125' . $k . '_u_u, zueci_cp125' . $k . '_sb_u,';
    $out[] = '                          ZUECI_ASIZE(zueci_cp125' . $k . '_sb_u) + 0x80, p_u);';
    $out[] = '}';
    $out[] = '#endif';
}

if (0) {
    $out[] = '';
    $out[] = '/* Total Windows 125x bytes: ' . $tot_cp125x . ' */';

    $out[] = '';
    $out[] = '/* Total bytes: ' . ($tot_cp437 + $tot_8859 + $tot_cp125x) . ' */';
}

$out[] = '';
$out[] = '#endif /* ZUECI_SB_H */';

file_put_contents($out_dirname . '/zueci_sb.h', implode("\n", $out) . "\n");

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