/* Modified for use with ttf2eot, originally from WebKit. */

/*
 * Copyright (C) 2008 Apple Inc.  All rights reserved.
 *
 * 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.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``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 APPLE COMPUTER, INC. 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. 
 */

#include <string.h>
#include <stddef.h>

#include <vector>

#ifndef _MSC_VER
# include <stdint.h>
#else
typedef unsigned char uint8_t;
#endif

#include "OpenTypeUtilities.h"


using std::vector;

typedef unsigned Fixed;

#define DEFAULT_CHARSET 1

struct BigEndianUShort { 
    operator unsigned short() const { return (v & 0x00ff) << 8 | v >> 8; }
    BigEndianUShort(unsigned short u) : v((u & 0x00ff) << 8 | u >> 8) { }
    unsigned short v;
};

struct BigEndianULong { 
    operator unsigned() const { return (v & 0xff) << 24 | (v & 0xff00) << 8 | (v & 0xff0000) >> 8 | v >> 24; }
    BigEndianULong(unsigned u) : v((u & 0xff) << 24 | (u & 0xff00) << 8 | (u & 0xff0000) >> 8 | u >> 24) { }
    unsigned v;
};

#pragma pack(1)

struct EOTPrefix {
    unsigned eotSize;
    unsigned fontDataSize;
    unsigned version;
    unsigned flags;
    uint8_t fontPANOSE[10];
    uint8_t charset;
    uint8_t italic;
    unsigned weight;
    unsigned short fsType;
    unsigned short magicNumber;
    unsigned unicodeRange[4];
    unsigned codePageRange[2];
    unsigned checkSumAdjustment;
    unsigned reserved[4];
    unsigned short padding1;
};

struct TableDirectoryEntry {
    BigEndianULong tag;
    BigEndianULong checkSum;
    BigEndianULong offset;
    BigEndianULong length;
};

struct sfntHeader {
    Fixed version;
    BigEndianUShort numTables;
    BigEndianUShort searchRange;
    BigEndianUShort entrySelector;
    BigEndianUShort rangeShift;
    TableDirectoryEntry tables[1];
};

struct OS2Table {
    BigEndianUShort version;
    BigEndianUShort avgCharWidth;
    BigEndianUShort weightClass;
    BigEndianUShort widthClass;
    BigEndianUShort fsType;
    BigEndianUShort subscriptXSize;
    BigEndianUShort subscriptYSize;
    BigEndianUShort subscriptXOffset;
    BigEndianUShort subscriptYOffset;
    BigEndianUShort superscriptXSize;
    BigEndianUShort superscriptYSize;
    BigEndianUShort superscriptXOffset;
    BigEndianUShort superscriptYOffset;
    BigEndianUShort strikeoutSize;
    BigEndianUShort strikeoutPosition;
    BigEndianUShort familyClass;
    uint8_t panose[10];
    BigEndianULong unicodeRange[4];
    uint8_t vendID[4];
    BigEndianUShort fsSelection;
    BigEndianUShort firstCharIndex;
    BigEndianUShort lastCharIndex;
    BigEndianUShort typoAscender;
    BigEndianUShort typoDescender;
    BigEndianUShort typoLineGap;
    BigEndianUShort winAscent;
    BigEndianUShort winDescent;
    BigEndianULong codePageRange[2];
    BigEndianUShort xHeight;
    BigEndianUShort capHeight;
    BigEndianUShort defaultChar;
    BigEndianUShort breakChar;
    BigEndianUShort maxContext;
};

struct headTable {
    Fixed version;
    Fixed fontRevision;
    BigEndianULong checkSumAdjustment;
    BigEndianULong magicNumber;
    BigEndianUShort flags;
    BigEndianUShort unitsPerEm;
    long long created;
    long long modified;
    BigEndianUShort xMin;
    BigEndianUShort xMax;
    BigEndianUShort yMin;
    BigEndianUShort yMax;
    BigEndianUShort macStyle;
    BigEndianUShort lowestRectPPEM;
    BigEndianUShort fontDirectionHint;
    BigEndianUShort indexToLocFormat;
    BigEndianUShort glyphDataFormat;
};

struct nameRecord {
    BigEndianUShort platformID;
    BigEndianUShort encodingID;
    BigEndianUShort languageID;
    BigEndianUShort nameID;
    BigEndianUShort length;
    BigEndianUShort offset;
};

struct nameTable {
    BigEndianUShort format;
    BigEndianUShort count;
    BigEndianUShort stringOffset;
    nameRecord nameRecords[1];
};

#pragma pack()

static void appendBigEndianStringToEOTHeader(vector<uint8_t>&eotHeader, const BigEndianUShort* string, unsigned short length)
{
    size_t size = eotHeader.size();
    eotHeader.resize(size + length + 2 * sizeof(unsigned short));
    unsigned short* dst = reinterpret_cast<unsigned short*>(&eotHeader[0] + size);
    unsigned i = 0;
    dst[i++] = length;
    unsigned numCharacters = length / 2;
    for (unsigned j = 0; j < numCharacters; j++)
        dst[i++] = string[j];
    dst[i] = 0;
}

bool getEOTHeader(unsigned char* fontData, size_t fontSize, vector<uint8_t>& eotHeader, size_t& overlayDst, size_t& overlaySrc, size_t& overlayLength)
{
    overlayDst = 0;
    overlaySrc = 0;
    overlayLength = 0;

    size_t dataLength = fontSize;
    const char* data = (const char *) fontData;

    eotHeader.resize(sizeof(EOTPrefix));
    EOTPrefix* prefix = reinterpret_cast<EOTPrefix*>(&eotHeader[0]);

    prefix->fontDataSize = dataLength;
    prefix->version = 0x00020001;
    prefix->flags = 0;

    if (dataLength < offsetof(sfntHeader, tables))
        return false;

    const sfntHeader* sfnt = reinterpret_cast<const sfntHeader*>(data);

    if (dataLength < offsetof(sfntHeader, tables) + sfnt->numTables * sizeof(TableDirectoryEntry))
        return false;

    bool haveOS2 = false;
    bool haveHead = false;
    bool haveName = false;

    const BigEndianUShort* familyName = 0;
    unsigned short familyNameLength = 0;
    const BigEndianUShort* subfamilyName = 0;
    unsigned short subfamilyNameLength = 0;
    const BigEndianUShort* fullName = 0;
    unsigned short fullNameLength = 0;
    const BigEndianUShort* versionString = 0;
    unsigned short versionStringLength = 0;

    for (unsigned i = 0; i < sfnt->numTables; i++) {
        unsigned tableOffset = sfnt->tables[i].offset;
        unsigned tableLength = sfnt->tables[i].length;

        if (dataLength < tableOffset || dataLength < tableLength || dataLength < tableOffset + tableLength)
            return false;

        unsigned tableTag = sfnt->tables[i].tag;
        switch (tableTag) {
            case 'OS/2':
                {
                    if (dataLength < tableOffset + sizeof(OS2Table))
                        return false;

                    haveOS2 = true;
                    const OS2Table* OS2 = reinterpret_cast<const OS2Table*>(data + tableOffset);
                    for (unsigned j = 0; j < 10; j++)
                        prefix->fontPANOSE[j] = OS2->panose[j];
                    prefix->italic = OS2->fsSelection & 0x01;
                    prefix->weight = OS2->weightClass;
                    // FIXME: Should use OS2->fsType, but some TrueType fonts set it to an over-restrictive value.
                    // Since ATS does not enforce this on Mac OS X, we do not enforce it either.
                    prefix->fsType = 0;            
                    for (unsigned j = 0; j < 4; j++)
                        prefix->unicodeRange[j] = OS2->unicodeRange[j];
                    for (unsigned j = 0; j < 2; j++)
                        prefix->codePageRange[j] = OS2->codePageRange[j];
                    break;
                }
            case 'head':
                {
                    if (dataLength < tableOffset + sizeof(headTable))
                        return false;

                    haveHead = true;
                    const headTable* head = reinterpret_cast<const headTable*>(data + tableOffset);
                    prefix->checkSumAdjustment = head->checkSumAdjustment;
                    break;
                }
            case 'name':
                {
                    if (dataLength < tableOffset + offsetof(nameTable, nameRecords))
                        return false;

                    haveName = true;
                    const nameTable* name = reinterpret_cast<const nameTable*>(data + tableOffset);
                    for (int j = 0; j < name->count; j++) {
                        if (dataLength < tableOffset + offsetof(nameTable, nameRecords) + (j + 1) * sizeof(nameRecord))
                            return false;
                        if (name->nameRecords[j].platformID == 3 && name->nameRecords[j].encodingID == 1 && name->nameRecords[j].languageID == 0x0409) {
                            if (dataLength < tableOffset + name->stringOffset + name->nameRecords[j].offset + name->nameRecords[j].length)
                                return false;

                            unsigned short nameLength = name->nameRecords[j].length;
                            const BigEndianUShort* nameString = reinterpret_cast<const BigEndianUShort*>(data + tableOffset + name->stringOffset + name->nameRecords[j].offset);
                            
                            switch (name->nameRecords[j].nameID) {
                                case 1:
                                    familyNameLength = nameLength;
                                    familyName = nameString;
                                    break;
                                case 2:
                                    subfamilyNameLength = nameLength;
                                    subfamilyName = nameString;
                                    break;
                                case 4:
                                    fullNameLength = nameLength;
                                    fullName = nameString;
                                    break;
                                case 5:
                                    versionStringLength = nameLength;
                                    versionString = nameString;
                                    break;
                                default:
                                    break;
                            }
                        }
                    }
                    break;
                }
            default:
                break;
        }
        if (haveOS2 && haveHead && haveName)
            break;
    }

    prefix->charset = DEFAULT_CHARSET;
    prefix->magicNumber = 0x504c;
    prefix->reserved[0] = 0;
    prefix->reserved[1] = 0;
    prefix->reserved[2] = 0;
    prefix->reserved[3] = 0;
    prefix->padding1 = 0;

    appendBigEndianStringToEOTHeader(eotHeader, familyName, familyNameLength);
    appendBigEndianStringToEOTHeader(eotHeader, subfamilyName, subfamilyNameLength);
    appendBigEndianStringToEOTHeader(eotHeader, versionString, versionStringLength);

    // If possible, ensure that the family name is a prefix of the full name.
    if (fullNameLength >= familyNameLength && memcmp(familyName, fullName, familyNameLength)) {
        overlaySrc = reinterpret_cast<const char*>(fullName) - data;
        overlayDst = reinterpret_cast<const char*>(familyName) - data;
        overlayLength = familyNameLength;
    }

    appendBigEndianStringToEOTHeader(eotHeader, fullName, fullNameLength);

    unsigned short padding = 0;
    eotHeader.push_back(padding);
    eotHeader.push_back(padding);

    prefix = reinterpret_cast<EOTPrefix*>(&eotHeader[0]);
    prefix->eotSize = eotHeader.size() + fontSize;

    return true;
}
