//========================================================================
//
// FoFiType1.cc
//
// Copyright 1999-2003 Glyph & Cog, LLC
//
//========================================================================

//========================================================================
//
// Modified under the Poppler project - http://poppler.freedesktop.org
//
// All changes made under the Poppler project to this file are licensed
// under GPL version 2 or later
//
// Copyright (C) 2005, 2008, 2010, 2018, 2021, 2022 Albert Astals Cid <aacid@kde.org>
// Copyright (C) 2005 Kristian Høgsberg <krh@redhat.com>
// Copyright (C) 2010 Jakub Wilk <jwilk@jwilk.net>
// Copyright (C) 2014 Carlos Garcia Campos <carlosgc@gnome.org>
// Copyright (C) 2017 Adrian Johnson <ajohnson@redneon.com>
// Copyright (C) 2017 Jean Ghali <jghali@libertysurf.fr>
// Copyright (C) 2022 Oliver Sander <oliver.sander@tu-dresden.de>
//
// To see a description of the changes please see the Changelog file that
// came with your tarball or type make ChangeLog if you are building from git
//
//========================================================================

#include <config.h>

#include <cstdlib>
#include <cstring>
#include <climits>
#include "goo/glibc.h"
#include "goo/gmem.h"
#include "goo/GooLikely.h"
#include "FoFiEncodings.h"
#include "FoFiType1.h"
#include "poppler/Error.h"

//------------------------------------------------------------------------
// FoFiType1
//------------------------------------------------------------------------

FoFiType1 *FoFiType1::make(const unsigned char *fileA, int lenA)
{
    return new FoFiType1(fileA, lenA, false);
}

FoFiType1 *FoFiType1::load(const char *fileName)
{
    char *fileA;
    int lenA;

    if (!(fileA = FoFiBase::readFile(fileName, &lenA))) {
        return nullptr;
    }
    return new FoFiType1((unsigned char *)fileA, lenA, true);
}

FoFiType1::FoFiType1(const unsigned char *fileA, int lenA, bool freeFileDataA) : FoFiBase(fileA, lenA, freeFileDataA)
{
    name = nullptr;
    encoding = nullptr;
    fontMatrix[0] = 0.001;
    fontMatrix[1] = 0;
    fontMatrix[2] = 0;
    fontMatrix[3] = 0.001;
    fontMatrix[4] = 0;
    fontMatrix[5] = 0;
    parsed = false;
    undoPFB();
}

FoFiType1::~FoFiType1()
{
    int i;

    if (name) {
        gfree(name);
    }
    if (encoding && encoding != fofiType1StandardEncoding) {
        for (i = 0; i < 256; ++i) {
            gfree(encoding[i]);
        }
        gfree(encoding);
    }
}

const char *FoFiType1::getName()
{
    if (!parsed) {
        parse();
    }
    return name;
}

char **FoFiType1::getEncoding()
{
    if (!parsed) {
        parse();
    }
    return encoding;
}

void FoFiType1::getFontMatrix(double *mat)
{
    int i;

    if (!parsed) {
        parse();
    }
    for (i = 0; i < 6; ++i) {
        mat[i] = fontMatrix[i];
    }
}

void FoFiType1::writeEncoded(const char **newEncoding, FoFiOutputFunc outputFunc, void *outputStream) const
{
    char buf[512];
    char *line, *line2, *p;
    int i;

    // copy everything up to the encoding
    for (line = (char *)file; line && strncmp(line, "/Encoding", 9); line = getNextLine(line)) {
        ;
    }
    if (!line) {
        // no encoding - just copy the whole font file
        (*outputFunc)(outputStream, (char *)file, len);
        return;
    }
    (*outputFunc)(outputStream, (char *)file, line - (char *)file);

    // write the new encoding
    (*outputFunc)(outputStream, "/Encoding 256 array\n", 20);
    (*outputFunc)(outputStream, "0 1 255 {1 index exch /.notdef put} for\n", 40);
    for (i = 0; i < 256; ++i) {
        if (newEncoding[i]) {
            sprintf(buf, "dup %d /%s put\n", i, newEncoding[i]);
            (*outputFunc)(outputStream, buf, strlen(buf));
        }
    }
    (*outputFunc)(outputStream, "readonly def\n", 13);

    // find the end of the encoding data
    //~ this ought to parse PostScript tokens
    if (!strncmp(line, "/Encoding StandardEncoding def", 30)) {
        line = getNextLine(line);
    } else {
        // skip "/Encoding" + one whitespace char,
        // then look for 'def' preceded by PostScript whitespace
        p = line + 10;
        line = nullptr;
        for (; p < (char *)file + len; ++p) {
            if ((*p == ' ' || *p == '\t' || *p == '\x0a' || *p == '\x0d' || *p == '\x0c' || *p == '\0') && p + 4 <= (char *)file + len && !strncmp(p + 1, "def", 3)) {
                line = p + 4;
                break;
            }
        }
    }

    // some fonts have two /Encoding entries in their dictionary, so we
    // check for a second one here
    if (line) {
        for (line2 = line, i = 0; i < 20 && line2 && strncmp(line2, "/Encoding", 9); line2 = getNextLine(line2), ++i) {
            ;
        }
        if (i < 20 && line2) {
            (*outputFunc)(outputStream, line, line2 - line);
            if (!strncmp(line2, "/Encoding StandardEncoding def", 30)) {
                line = getNextLine(line2);
            } else {
                // skip "/Encoding" + one whitespace char,
                // then look for 'def' preceded by PostScript whitespace
                p = line2 + 10;
                line = nullptr;
                for (; p < (char *)file + len; ++p) {
                    if ((*p == ' ' || *p == '\t' || *p == '\x0a' || *p == '\x0d' || *p == '\x0c' || *p == '\0') && p + 4 <= (char *)file + len && !strncmp(p + 1, "def", 3)) {
                        line = p + 4;
                        break;
                    }
                }
            }
        }

        // copy everything after the encoding
        if (line) {
            (*outputFunc)(outputStream, line, ((char *)file + len) - line);
        }
    }
}

char *FoFiType1::getNextLine(char *line) const
{
    while (line < (char *)file + len && *line != '\x0a' && *line != '\x0d') {
        ++line;
    }
    if (line < (char *)file + len && *line == '\x0d') {
        ++line;
    }
    if (line < (char *)file + len && *line == '\x0a') {
        ++line;
    }
    if (line >= (char *)file + len) {
        return nullptr;
    }
    return line;
}

void FoFiType1::parse()
{
    char *line, *line1, *firstLine, *p, *p2;
    char buf[256];
    char c;
    int n, code, base, i, j;
    char *tokptr;
    bool gotMatrix, continueLine;

    gotMatrix = false;
    for (i = 1, line = (char *)file; i <= 100 && line && (!name || !encoding); ++i) {

        // get font name
        if (!name && (line + 9 <= (char *)file + len) && !strncmp(line, "/FontName", 9)) {
            const auto availableFile = (char *)file + len - line;
            const int lineLen = static_cast<int>(availableFile < 255 ? availableFile : 255);
            strncpy(buf, line, lineLen);
            buf[lineLen] = '\0';
            if ((p = strchr(buf + 9, '/')) && (p = strtok_r(p + 1, " \t\n\r", &tokptr))) {
                name = copyString(p);
            }
            line = getNextLine(line);

            // get encoding
        } else if (!encoding && (line + 30 <= (char *)file + len) && !strncmp(line, "/Encoding StandardEncoding def", 30)) {
            encoding = (char **)fofiType1StandardEncoding;
        } else if (!encoding && (line + 19 <= (char *)file + len) && !strncmp(line, "/Encoding 256 array", 19)) {
            encoding = (char **)gmallocn(256, sizeof(char *));
            for (j = 0; j < 256; ++j) {
                encoding[j] = nullptr;
            }
            continueLine = false;
            for (j = 0, line = getNextLine(line); j < 300 && line && (line1 = getNextLine(line)); ++j, line = line1) {
                if ((n = (int)(line1 - line)) > 255) {
                    error(errSyntaxWarning, -1, "FoFiType1::parse a line has more than 255 characters, we don't support this");
                    n = 255;
                }
                if (continueLine) {
                    continueLine = false;
                    if ((line1 - firstLine) + 1 > (int)sizeof(buf)) {
                        break;
                    }
                    p = firstLine;
                    p2 = buf;
                    while (p < line1) {
                        if (*p == '\n' || *p == '\r') {
                            *p2++ = ' ';
                            p++;
                        } else {
                            *p2++ = *p++;
                        }
                    }
                    *p2 = '\0';
                } else {
                    firstLine = line;
                    strncpy(buf, line, n);
                    buf[n] = '\0';
                }
                for (p = buf; *p == ' ' || *p == '\t'; ++p) {
                    ;
                }
                if (!strncmp(p, "dup", 3)) {
                    while (true) {
                        p += 3;
                        for (; *p == ' ' || *p == '\t'; ++p) {
                            ;
                        }
                        code = 0;
                        if (*p == '8' && p[1] == '#') {
                            base = 8;
                            p += 2;
                        } else if (*p >= '0' && *p <= '9') {
                            base = 10;
                        } else if (*p == '\n' || *p == '\r') {
                            continueLine = true;
                            break;
                        } else {
                            break;
                        }
                        for (; *p >= '0' && *p < '0' + base && code < INT_MAX / (base + (*p - '0')); ++p) {
                            code = code * base + (*p - '0');
                        }
                        for (; *p == ' ' || *p == '\t'; ++p) {
                            ;
                        }
                        if (*p == '\n' || *p == '\r') {
                            continueLine = true;
                            break;
                        } else if (*p != '/') {
                            break;
                        }
                        ++p;
                        for (p2 = p; *p2 && *p2 != ' ' && *p2 != '\t'; ++p2) {
                            ;
                        }
                        if (code >= 0 && code < 256) {
                            c = *p2;
                            *p2 = '\0';
                            gfree(encoding[code]);
                            encoding[code] = copyString(p);
                            *p2 = c;
                        }
                        for (p = p2; *p == ' ' || *p == '\t'; ++p) {
                            ;
                        }
                        if (*p == '\n' || *p == '\r') {
                            continueLine = true;
                            break;
                        }
                        if (strncmp(p, "put", 3)) {
                            break;
                        }
                        for (p += 3; *p == ' ' || *p == '\t'; ++p) {
                            ;
                        }
                        if (strncmp(p, "dup", 3)) {
                            break;
                        }
                    }
                } else {
                    if (strtok_r(buf, " \t", &tokptr) && (p = strtok_r(nullptr, " \t\n\r", &tokptr)) && !strcmp(p, "def")) {
                        break;
                    }
                }
            }
            //~ check for getinterval/putinterval junk

        } else if (!gotMatrix && (line + 11 <= (char *)file + len) && !strncmp(line, "/FontMatrix", 11)) {
            const auto availableFile = (char *)file + len - (line + 11);
            const int bufLen = static_cast<int>(availableFile < 255 ? availableFile : 255);
            strncpy(buf, line + 11, bufLen);
            buf[bufLen] = '\0';
            if ((p = strchr(buf, '['))) {
                ++p;
                if ((p2 = strchr(p, ']'))) {
                    *p2 = '\0';
                    for (j = 0; j < 6; ++j) {
                        if ((p = strtok_r(j == 0 ? p : nullptr, " \t\n\r", &tokptr))) {
                            fontMatrix[j] = atof(p);
                        } else {
                            break;
                        }
                    }
                }
            }
            gotMatrix = true;

        } else {
            line = getNextLine(line);
        }
    }

    parsed = true;
}

// Undo the PFB encoding, i.e., remove the PFB headers.
void FoFiType1::undoPFB()
{
    bool ok;
    unsigned char *file2;
    int pos1, pos2, type;
    unsigned int segLen;

    ok = true;
    if (getU8(0, &ok) != 0x80 || !ok) {
        return;
    }
    file2 = (unsigned char *)gmalloc(len);
    pos1 = pos2 = 0;
    while (getU8(pos1, &ok) == 0x80 && ok) {
        type = getU8(pos1 + 1, &ok);
        if (type < 1 || type > 2 || !ok) {
            break;
        }
        segLen = getU32LE(pos1 + 2, &ok);
        pos1 += 6;
        if (!ok || !checkRegion(pos1, segLen)) {
            break;
        }
        memcpy(file2 + pos2, file + pos1, segLen);
        pos1 += segLen;
        pos2 += segLen;
    }
    if (freeFileData) {
        gfree((char *)file);
    }
    file = file2;
    freeFileData = true;
    len = pos2;
}
