view colormap.c @ 1822:892d5277f1ff

Remove note about the search pattern parser being not very tolerant, it's somewhat better now.
author Matti Hamalainen <ccr@tnsp.org>
date Mon, 30 Oct 2017 12:55:21 +0200
parents cf0369e8aeff
children 79dd960610cb
line wrap: on
line source

/*
 * Convert BatMUD ASCII map to different formats
 * Programmed by Matti 'ccr' Hämäläinen (Ggr Pupunen)
 * (C) Copyright 2006-2015 Tecnic Software productions (TNSP)
 */
#include "libmaputils.h"
#include "th_args.h"
#include "th_string.h"

#define SET_MAX_COLORS    (256)
#define SET_MAX_STRLEN    (1024)


typedef struct
{
    char *fmtName;
    char *fmtDescription;
    BOOL supBackColor;
    void (*putTagMarker) (FILE *, const char *, const int color, const int marker);
    void (*putTagLocation) (FILE *, const char *, const int color);
    void (*putTagLocationEnd) (FILE *, const char *);
    void (*putTagStart) (FILE *, const int color);
    void (*putTagEnd) (FILE *);
    BOOL (*putTagEOL) (FILE *);
    void (*putFileStart) (FILE *);
    void (*putFileEnd) (FILE *);
    int  (*putString) (const char *str, FILE *);
} CMapOutFormat;


char    *inFilename = NULL,
        *outFilename = NULL;

char    *optXHTMLTagName = "i",
        *optMapTitle = NULL,
        *optUrchinFile = NULL;

BOOL    optPerformAnalysis = FALSE,
        optUseOldFormat = FALSE,
        optCheatMode = FALSE,
        optNoHeaders = FALSE,
        optPosGlue = FALSE,
        optCityFormat = FALSE;

int     optOutputFormat = 0,
        optBackColor = 0;


/* Arguments
 */
static const th_optarg optList[] =
{
    { 0, '?', "help",           "Show this help", OPT_NONE },
    { 1, 'v', "verbose",        "Be more verbose", OPT_NONE },
    { 2, 'q', "quiet",          "Be quiet", OPT_NONE },
    { 3, 'T', "html-tag",       "XHTML tag used for map", OPT_ARGREQ },
    { 4, 'a', "analysis",       "Perform statistical analysis", OPT_NONE },
    { 6, 'O', "old-format",     "Input using old symbols/colors", OPT_NONE },
    { 7, 'o', "output",         "Output filename", OPT_ARGREQ },
    { 8, 'f', "format",         "Specify output format", OPT_ARGREQ },
    { 9, 't', "title",          "Map title", OPT_ARGREQ },
    { 10,'C', "cheat-mode",     "Use cheating in HTML", OPT_NONE },
    { 11,'n', "no-headers",     "Do not output headers/footers", OPT_NONE },
    { 12,'P', "pos-glue",       "Generate JavaScript, etc. for posglue", OPT_NONE },
    { 13,'c', "city-format",    "Input is a city map", OPT_NONE },
    { 14,'u', "urchin-file",    "Specify urchin file", OPT_ARGREQ },
};

static const int optListN = sizeof(optList) / sizeof(optList[0]);


/*
 * ANSI output format functions
 */
void putTagStartANSI(FILE *outFile, const int c)
{
    fputc(0x1b, outFile);

    if (c < 0)
    {
        fprintf(outFile, "[0;%d;30m",
            10 + mapColors[-c-1].ansi);
    }
    else
    {
        fprintf(outFile, "[0;%d;%dm",
            mapColors[c].ansiAttr,
            mapColors[c].ansi);
    }
}

void putTagEndANSI(FILE *outFile)
{
    (void) outFile;
    /*
    fputc(0x1b, outFile);
    fprintf(outFile, "[0m");
    */
}

BOOL putEOLANSI(FILE *outFile)
{
    fprintf(outFile, "\n");
    return TRUE;
}

void putFileStartANSI(FILE *outFile)
{
    fputc(0x1b, outFile);
    fprintf(outFile, "[0m");

    if (optMapTitle)
    {
        fprintf(outFile, "%s\n", optMapTitle);
    }
}


void putFileEndANSI(FILE *outFile)
{
    fputc(0x1b, outFile);
    fprintf(outFile, "[0m");
}


/*
 * ASCII output format functions
 */
void putTagStartText(FILE *outFile, const int c)
{
    (void) outFile; (void) c;
//    fprintf(outFile, "§");
}

void putTagEndText(FILE *outFile)
{
    (void) outFile;
//    fprintf(outFile, "$");
}

BOOL putEOLText(FILE *outFile)
{
    fprintf(outFile, "\n");
    return FALSE;
}

void putFileStartText(FILE *outFile)
{
    if (optMapTitle)
    {
        fprintf(outFile, "%s\n", optMapTitle);
    }
}


/*
 * XHTML+CSS output format functions
 */
#define XHTML_LOCLABEL "label"
#define XHTML_LOCMARKER "marker"

void putColorClassXHTML(FILE *outFile, const int color)
{
    int q = 'a' + color;

    if (optCheatMode)
        fprintf(outFile, "class=%c>", q);
    else
        fprintf(outFile, "class=\"%c\">", q);
}

void putTagMarkerXHTML(FILE *outFile, const char *locID, const int color, const int marker)
{
    fprintf(outFile,
        "<i class=\"" XHTML_LOCMARKER " %c\" id=\"%s\">%c",
        'a' + color, locID, marker);
}

void putTagLocationXHTML(FILE *outFile, const char *locID, const int color)
{
    fprintf(outFile,
        "<i class=\"" XHTML_LOCLABEL "\"><a id=\"%s\" ",
        locID);
    putColorClassXHTML(outFile, color);
}

void putTagLocationEndXHTML(FILE *outFile, const char *locID)
{
    (void) locID;
    fprintf(outFile, "</a>");
}

void putTagStartXHTML(FILE *outFile, const int c)
{
    fprintf(outFile, "<i ");
    putColorClassXHTML(outFile, c);
}

void putTagEndXHTML(FILE *outFile)
{
    fprintf(outFile, "</i>");
}

BOOL putEOLXHTML(FILE *outFile)
{
    fprintf(outFile, "\n");
    return FALSE;
}

void putFileGenericHTML(FILE *outFile)
{
    char buf[32];

    fprintf(outFile,
    " <script type=\"text/javascript\" src=\"util.js\"></script>\n"
    " <style type=\"text/css\">\n"
    "  <!--\n"
    "  %s." XHTML_LOCLABEL " { background: white; color: black; %s}\n"
    "  body { background: black; color: %s; font-family: Arial, Verdana, sans-serif; }\n"
    "  h2 { color: white; font-size: 14pt; }\n"
    "  pre { font-size: 10pt; }\n"
    "  %s { text-decoration: none; font-style: normal; }\n",
    optXHTMLTagName, (optPosGlue) ? "position: relative; " : "",
    muColorToCSSColor(buf, sizeof(buf), optBackColor),
    optXHTMLTagName
    );

    muPrintHTMLcolors(outFile, optXHTMLTagName, NULL, NULL);
    muPrintHTMLcolors(outFile, "a", "background", " color: black;");

    if (optPosGlue)
    {
    fprintf(outFile,
    "  div.sgbox {\n"
    "    background: #bbb;\n"
    "    color: black;\n"
    "    position: fixed;\n"
    "    top: 0.5em;\n"
    "    right: 0.5em;\n"
    "    width: 25em;\n"
    "    height: 6em;\n"
    "    margin: 4px;\n"
    "    padding: 4px;\n"
    "    z-index: 10;\n"
    "    border: 2px solid gray;\n"
    "    font-size: 10pt;\n"
    "  }\n"
    "  i." XHTML_LOCLABEL " a {\n"
    "    position: absolute;\n"
    "    top: 0px;\n"
    "    left: 0px;\n"
    "    z-index: 2;\n"
    "    border: 2px solid black;\n"
    "    border-radius: 0.35em;\n"
    "    padding: 0.15em;\n"
    "    box-shadow: 2px 2px 4px black;\n"
    "  }\n"
    "  div.sbtitle {\n"
    "    font-size: 1.5em;\n"
    "    font-weight: bold;\n"
    "  }\n"
    "  i.label a.nactive {\n"
    "    border: 2px solid rgba(100,0,0, 0.5);\n"
    "    background: rgba(255,0,0, 0.7);\n"
    "    color: rgba(255,255,255, 0.8);\n"
    "    text-shadow: 1px 1px 1px #000;\n"
    "    font-weight: bold;\n"
    "    transform: scale(1.1);\n"
    "  }\n"
    "  i.marker.nactive {\n"
    "    animation: pulse 1s ease infinite;\n"
    "  }\n"
    "  @keyframes pulse {\n"
    "    0%% { background: #000; }\n"
    "    50%% { background: #f00; }\n"
    "    100%% { background: #000; }\n"
    "  }\n"
    );
    }

    fprintf(outFile,
    " -->\n"
    " </style>\n"
    );

    if (optUrchinFile)
    {
        if (muCopyFileToStream(outFile, optUrchinFile) < 0)
        {
            THERR("Error copying urchin file '%s' to output!\n", optUrchinFile);
            exit(17);
        }
    }

    fprintf(outFile,
    "</head>\n"
    "<body onLoad=\"jsGotoOnLoadPos();\">\n");

    if (!optPosGlue)
    {
        if (optMapTitle)
        {
            fprintf(outFile, "<h2>");
            fputse(optMapTitle, outFile);
            fprintf(outFile, "</h2>\n");
        }
    }
    else
    {
        fprintf(outFile,
        "<div id=\"sbox\" class=\"sgbox\">\n"
        " <div class=\"sbtitle\">");
        fputse(optMapTitle, outFile);
        fprintf(outFile,
        "</div>\n"
        " <form>\n"
        "  <select id=\"slocation\" onChange=\"jsGotoPos();\" autofocus=\"autofocus\">\n"
        "@LOCATIONS@\n"
        "  </select>\n"
        "  <br />\n"
        "  <input id=\"shide\" onClick=\"jsToggleLabels();\" type=\"checkbox\"%s><label for=\"shide\">Labels</label>\n"
        "  <input id=\"sscroll\" type=\"checkbox\"%s><label for=\"sscroll\">Smooth scroll</label>\n"
        " </form>\n"
        "</div>\n",
        (1 ? " checked=\"checked\"" : ""),
        (0 ? " checked=\"checked\"" : "")
        );
    }

    fprintf(outFile,
    "<pre>\n"
    );
}

void putFileStartXHTML(FILE *outFile)
{
    muPrintHTMLhead(outFile, optMapTitle, FALSE);
    putFileGenericHTML(outFile);
}

void putFileEndXHTML(FILE *outFile)
{
    // XHTML document end tags
    fprintf(outFile,
    "</pre>\n"
    "</body>\n"
    "</html>\n"
    );
}


/*
 * XHTML+CSS output format functions
 */
void putFileStartHTML5(FILE *outFile)
{
    muPrintHTMLhead(outFile, optMapTitle, TRUE);
    putFileGenericHTML(outFile);
}



/* Process a normal format input
 */
void checkEndTag(FILE *outFile, const CMapOutFormat *fmt, const int prevColor)
{
    if (prevColor != -1 && (!fmt->supBackColor || prevColor != optBackColor))
        fmt->putTagEnd(outFile);
}


BOOL getTagStr(FILE *inFile, char *tmpStr, const size_t len, const int endch)
{
    size_t i;
    int mch = EOF;

    for (i = 0; i < len && (mch = fgetc(inFile)) != EOF;)
    {
        if (mch == endch)
            break;
        else
            tmpStr[i++] = mch;
    }

    tmpStr[i] = 0;

    if (mch == EOF)
    {
        THERR("Unexpected end of file.\n");
        return FALSE;
    }
    else
    if (mch != endch)
    {
        THERR("No end tag 0x%02x found.\n", endch);
        return FALSE;
    }

    return TRUE;
}


BOOL processData(FILE *inFile, FILE *outFile, const CMapOutFormat *fmt)
{
    int k, currColor, prevColor;

    currColor = prevColor = -1;
    while ((k = fgetc(inFile)) != EOF)
    {
        if (k == '\n')
        {
            if (fmt->putTagEOL(outFile))
                currColor = -1;
        }
        else
        if (k == 0xfb)
        {
            char tmpStr[SET_MAX_STRLEN];
            int mch;

            // Location marker mode
            checkEndTag(outFile, fmt, prevColor);

            // Get location marker tag
            if (!getTagStr(inFile, tmpStr, SET_MAX_STRLEN, 0xfc))
                return FALSE;

            // Get marker character
            mch = fgetc(inFile);
            k = fgetc(inFile);

            if (k != 0xfe)
            {
                THERR("Expected location tag '%s' end, but did not find one.\n", tmpStr);
                return FALSE;
            }

            currColor = muGetMapPieceColor(mch, optUseOldFormat, optCityFormat);
            if (fmt->putTagMarker != NULL)
                fmt->putTagMarker(outFile, tmpStr, currColor, mch);
            else
                fprintf(outFile, "%c", mch);

            currColor = prevColor = -2;
        }
        else
        if (k == 0xff)
        {
            char tmpStr[SET_MAX_STRLEN], tmpStr2[SET_MAX_STRLEN];
            int mcol;

            // Location title mode
            checkEndTag(outFile, fmt, prevColor);

            // Get location marker tag
            if (!getTagStr(inFile, tmpStr, SET_MAX_STRLEN, 0xfc))
                return FALSE;

            // Get color
            mcol = fgetc(inFile);
            if (mcol == EOF)
            {
                THERR("Unexpected end of file (location tag colour).\n");
                return FALSE;
            }

            // Get location name
            if (!getTagStr(inFile, tmpStr2, SET_MAX_STRLEN, 0xfe))
            {
                THERR("Expected location tag '%s' end, but did not find one.\n", tmpStr);
                return FALSE;
            }

            if (!fmt->putTagLocation && fmt->putTagStart)
                fmt->putTagStart(outFile, -mcol - 1);

            if (fmt->putTagLocation)
                fmt->putTagLocation(outFile, tmpStr, mcol);

            if (fmt->putString)
                fmt->putString(tmpStr2, outFile);
            else
                fputs(tmpStr2, outFile);

            if (fmt->putTagLocationEnd)
                fmt->putTagLocationEnd(outFile, tmpStr);

            currColor = prevColor = -2;
        }
        else
        {
            currColor = muGetMapPieceColor(k, optUseOldFormat, optCityFormat);
            if (currColor != prevColor)
            {
                checkEndTag(outFile, fmt, prevColor);

                if ((!fmt->supBackColor || currColor != optBackColor) && fmt->putTagStart)
                    fmt->putTagStart(outFile, currColor);

                fprintf(outFile, "%c", k);
            }
            else
            {
                fprintf(outFile, "%c", k);
            }
        }

        prevColor = currColor;
    }

    checkEndTag(outFile, fmt, prevColor);

    return TRUE;
}


/* Get a symbol
 */
char getSymbol(int i, BOOL useOld)
{
    if (useOld)
        return mapPieces[i].oldSymbol;
    else
        return mapPieces[i].symbol;
}


/* List of output formats
 */
CMapOutFormat outputFormats[] =
{
    { "xhtml", "XHTML+CSS", TRUE,
    putTagMarkerXHTML,
    putTagLocationXHTML, putTagLocationEndXHTML,
    putTagStartXHTML, putTagEndXHTML, putEOLXHTML,
    putFileStartXHTML, putFileEndXHTML, fputse
    },

    { "html5", "HTML5+CSS", TRUE,
    putTagMarkerXHTML,
    putTagLocationXHTML, putTagLocationEndXHTML,
    putTagStartXHTML, putTagEndXHTML, putEOLXHTML,
    putFileStartHTML5, putFileEndXHTML, fputse
    },

    { "ansi", "ANSI text", FALSE,
    NULL, NULL, NULL,
    putTagStartANSI, putTagEndANSI, putEOLANSI,
    putFileStartANSI, putFileEndANSI, NULL
    },

    { "text", "Plain ASCII text", FALSE,
    NULL, NULL, NULL,
    putTagStartText, putTagEndText, putEOLText,
    putFileStartText, NULL, NULL
    },
};

const int noutputFormats = sizeof(outputFormats) / sizeof(outputFormats[0]);


void argShowHelp(void)
{
    th_print_banner(stdout, th_prog_name,
        "[options] <input mapfile>");

    th_args_help(stdout, optList, optListN, 0);

    fprintf(stdout, "\nAvailable OUTPUT formats:\n");
    for (int i = 0; i < noutputFormats; i++)
    {
        fprintf(stdout, "  %-8s - %s %s\n",
            outputFormats[i].fmtName,
            outputFormats[i].fmtDescription,
            (i == optOutputFormat) ? "(default)" : ""
            );
    }

    fprintf(stdout, "\n");
}


BOOL argHandleOpt(const int optN, char *optArg, char *currArg)
{
    int i, n;

    switch (optN)
    {
    case 0:
        argShowHelp();
        exit(0);
        break;

    case 1:
        th_verbosityLevel++;
        break;

    case 2:
        th_verbosityLevel = -1;
        break;

    case 3:
        optXHTMLTagName = optArg;
        THMSG(2, "HTML tag set to '%s'\n", optXHTMLTagName);
        break;

    case 4:
        optPerformAnalysis = TRUE;
        THMSG(2, "Analysis enabled.\n");
        break;

    case 6:
        optUseOldFormat = TRUE;
        THMSG(2, "Input is using old map symbols/colors.\n");
        break;

    case 7:
        outFilename = optArg;
        THMSG(2, "Output file set to '%s'.\n", outFilename);
        break;

    case 8:
        // Get format
        for (i = 0, n = -1; (i < noutputFormats) && (n < 0); i++)
            if (strcmp(optArg, outputFormats[i].fmtName) == 0)
                n = i;

        if (n < 0)
        {
            THERR("Invalid output format '%s'\n", optArg);
            return FALSE;
        }

        optOutputFormat = n;
        THMSG(2, "Output format set to '%s'\n", optArg);
        break;

    case 9:
        optMapTitle = optArg;
        THMSG(2, "Map title string set to '%s'.\n", optMapTitle);
        break;

    case 10:
        optCheatMode = TRUE;
        THMSG(2, "HTML cheats mode enabled!\n");
        break;

    case 11:
        optNoHeaders = TRUE;
        THMSG(2, "Not outputting headers/footers\n");
        break;

    case 12:
        optPosGlue = TRUE;
        THMSG(2, "Generating JavaScript junk for positioning glue\n");
        break;

    case 13:
        optCityFormat = TRUE;
        THMSG(2, "Input is handled as a city map\n");
        break;

    case 14:
        optUrchinFile = optArg;
        THMSG(2, "Urchin filename set to '%s'.\n", optUrchinFile);
        break;

    default:
        THERR("Unknown option '%s'.\n", currArg);
        return FALSE;
    }

    return TRUE;
}


BOOL argHandleFile(char *currArg)
{
    if (!inFilename)
        inFilename = currArg;
    else
    {
        THERR("Too many input map files specified!\n");
        return FALSE;
    }

    return TRUE;
}


/* Main program
 */
int main(int argc, char *argv[])
{
    FILE *inFile = NULL, *outFile = NULL;
    CMapOutFormat *fmt = NULL;

    // Initialize
    th_init("colormap", "ASCII map colorizer", "0.5", NULL, NULL);
    th_verbosityLevel = 0;

    // Parse arguments
    if (!th_args_process(argc, argv, optList, optListN,
        argHandleOpt, argHandleFile, OPTH_BAILOUT))
        exit(1);

    if (inFilename == NULL)
    {
        THERR("Nothing to do. (try --help)\n");
        exit(0);
    }

    // Do statistical analysis, if needed
    if (optPerformAnalysis)
    {
        int k, c, p, i, tMax, fMax, tn, fn;
        int colChangesTo[SET_MAX_COLORS];
        int colChangesFrom[SET_MAX_COLORS];

        THMSG(1, "Performing statistical frequency analysis ...\n");

        THMSG(2, "Reading '%s' for analysis data ...\n",
            inFilename);

        if ((inFile = fopen(inFilename, "rb")) == NULL)
        {
            THERR("Error opening input file '%s'!\n",
                inFilename);
            exit(1);
        }

        // Initialize counters
        memset(colChangesTo, 0, sizeof(colChangesTo));
        memset(colChangesFrom, 0, sizeof(colChangesFrom));

        // Read data, keeping statistics of colour change frequencies
        c = p = -1;
        while ((k = fgetc(inFile)) != EOF)
        {
            if (k != '\n' && k != ' ' && k < 0xfb)
            {
                c = muGetMapPieceColor(k, optUseOldFormat, optCityFormat);
                if (c != p && c >= 0 && c < SET_MAX_COLORS)
                {
                    colChangesTo[c]++;

                    if (p >= 0 && p < SET_MAX_COLORS)
                        colChangesFrom[p]++;
                }
            }
            p = c;
        }

        fclose(inFile);

        // Find highest frequency
        THMSG(2, "Computing results.\n");

        tMax = fMax = tn = fn = -1;
        for (i = 0; i < SET_MAX_COLORS; i++)
        {
            if (colChangesTo[i] > tMax)
            {
                tMax = colChangesTo[i];
                tn = i;
            }
            if (colChangesFrom[i] > fMax)
            {
                fMax = colChangesFrom[i];
                fn = i;
            }
        }

        THMSG(2, "tMax=%d, tn=%d -- fMax=%d, fn=%d\n",
            tMax, tn, fMax, fn);

        if (tMax > fMax)
            optBackColor = tn;
        else
            optBackColor = fn;

        THMSG(1, "Using colour %d as 'background' color.\n",
            optBackColor);
    }

    // Open input file
    if ((inFile = fopen(inFilename, "rb")) == NULL)
    {
        THERR("Error opening input file '%s'!\n",
            inFilename);
        goto out;
    }

    // Open output file
    if (outFilename == NULL)
        outFile = stdout;
    else
    if ((outFile = fopen(outFilename, "wb")) == NULL)
    {
        THERR("Error opening output file '%s'!\n",
            outFilename);
        goto out;
    }

    // Okay, let's process the shit
    fmt = &outputFormats[optOutputFormat];
    THMSG(1, "Converting to '%s' ...\n", fmt->fmtName);

    if (!optNoHeaders && fmt->putFileStart)
        fmt->putFileStart(outFile);

    processData(inFile, outFile, fmt);

    if (!optNoHeaders && fmt->putFileEnd)
        fmt->putFileEnd(outFile);

out:
    if (outFile != NULL)
        fclose(outFile);

    if (inFile != NULL)
        fclose(inFile);

    THMSG(1, "Done.\n");

    exit(0);
    return 0;
}