/* Copyright 2014 Adobe Systems Incorporated (http://www.adobe.com/). All Rights Reserved.
   This software is licensed as OpenSource, under the Apache License, Version 2.0. This license is available at: http://opensource.org/licenses/Apache-2.0. */
/***********************************************************************/

/*
 * OS/2 and Windows Metrics.
 */

#include "OS_2.h"
#include "map.h"

#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h> /* to get getenv */

/* ---------------------------- Table Definition --------------------------- */

#define PANOSE_SIZE 10
#define VEND_ID_SIZE 4

typedef struct {
    uint16_t version;
    int16_t xAvgCharWidth;
    uint16_t usWeightClass;
#define FWEIGHT_NORMAL 400
    uint16_t usWidthClass;
#define FWIDTH_NORMAL 5
    uint16_t fsType; /* Embedding permissions */
#define EMBED_NONE (1 << 1)
#define EMBED_PRINT_AND_VIEW (1 << 2)
#define EMBED_EDITABLE (1 << 3)
    int16_t ySubscriptXSize;
    int16_t ySubscriptYSize;
    int16_t ySubscriptXOffset;
    int16_t ySubscriptYOffset;
    int16_t ySuperscriptXSize;
    int16_t ySuperscriptYSize;
    int16_t ySuperscriptXOffset;
    int16_t ySuperscriptYOffset;
    int16_t yStrikeoutSize;
    int16_t yStrikeoutPosition;
    uint16_t sFamilyClass;  /* spec defines this as signed */
#define CLASS_NONE 0
    char panose[PANOSE_SIZE];
#define PANOSE_ANY 0    /* [any] */
#define PANOSE_NO_FIT 1 /* [no fit] */
#define PANOSE0_TEXT 2
#define PANOSE0_SCRIPT 3
#define PANOSE0_DECORATIVE 4
#define PANOSE0_PICTORIAL 5
#define PANOSE1_COVE 2
#define PANOSE1_SANS 11
#define PANOSE3_EXPANDED 5
#define PANOSE3_CONDENSED 6
#define PANOSE3_VERY_EXPANDED 7
#define PANOSE3_VERY_CONDENSED 8
#define PANOSE3_MONOSPACED 9
    uint32_t ulUnicodeRange1;
    uint32_t ulUnicodeRange2;
    uint32_t ulUnicodeRange3;
    uint32_t ulUnicodeRange4;
    char achVendId[VEND_ID_SIZE];
    uint16_t fsSelection;
#define SEL_ITALIC (1 << 0)
#define SEL_BOLD (1 << 5)
#define SEL_REGULAR (1 << 6)
#define SEL_RESPECT_STYPO (1 << 7)

    uint16_t usFirstCharIndex;
    uint16_t usLastCharIndex;
    int16_t sTypoAscender;
    int16_t sTypoDescender;
    int16_t sTypoLineGap;
    uint16_t usWinAscent;
    uint16_t usWinDescent;
    uint32_t ulCodePageRange1;              /* Version 1 */
    uint32_t ulCodePageRange2;              /* Version 1 */
    int16_t sXHeight;                         /* Version 2 */
    int16_t sCapHeight;                       /* Version 2 */
    uint16_t usDefaultChar;           /* Version 2 */
    uint16_t usBreakChar;             /* Version 2 */
    uint16_t usMaxContext;            /* Version 2 */
    uint16_t usLowerOpticalPointSize; /* Version 5 */
    uint16_t usUpperOpticalPointSize; /* Version 5 */
} OS_2Tbl;

/* --------------------------- Context Definition -------------------------- */

/* Set of fields which can or must be set by a call to this module */
enum {
    kUnicodeRanges,
    kCodePageRanges,
    kCharIndexRange,
    kMaxContext,
    kPanose,
    kFSType,
    kWeightClass,
    kWidthClass,

    kFieldCount
};

struct OS_2Ctx_ {
    OS_2Tbl tbl;            /* Table data */
    char seen[kFieldCount]; /* Flags keys seen */
    hotCtx g;               /* Package context */
};

/* --------------------------- Standard Functions -------------------------- */

/* Initialize field seen array. */
static void initFieldSeen(OS_2Ctx h) {
    memset(h->seen, 0, sizeof(h->seen));
}

void OS_2New(hotCtx g) {
    OS_2Ctx h = MEM_NEW(g, sizeof(struct OS_2Ctx_));

    initFieldSeen(h);
    /* Following is not necessarily set by processing of normal defaults. */
    h->tbl.usMaxContext = 0;
    h->tbl.usLowerOpticalPointSize = 0;
    h->tbl.usUpperOpticalPointSize = 0;
    h->tbl.sFamilyClass = CLASS_NONE;

    /* Link contexts */
    h->g = g;
    g->ctx.OS_2 = h;
}

/* Set weight and width classes from FullName */
static void setClasses(hotCtx g, OS_2Ctx h) {
    typedef struct {
        char *string;
        uint16_t value;
    } Match;
    static Match weight[] = {
        {"thin", 250},
        {"extralight", 250},
        {"ultralight", 250},
        {"light", 300}, /* Must succeed other lights */
        {"medium", 500},
        {"semibold", 600},
        {"demibold", 600},
        {"demi", 600}, /* Must succeed demibold */
        {"extrabold", 800},
        {"ultrabold", 800},
        {"extrablack", 950}, /* added */
        {"ultrablack", 950}, /* added */
        {"ultra", 900},      /* We need a special hack avoid seeing the width keyword "ultracondensed" as being "ultra". See code below. */
        {"super", 800},
        {"bold", 700}, /* Must succeed other bolds */
        {"black", 900},
        {"poster", 900},
        {"heavy", 850}, /* changed */
        {"nord", 900},
    };
    static Match width[] = {
        {"ultracondensed", 1},
        {"extracompressed", 1},
        {"ultracompressed", 1},
        {"extracondensed", 2},
        {"compressed", 2},
        {"semicondensed", 4},
        {"compact", 4},
        {"narrow", 4},
        {"condensed", 3}, /* Must succeed other condenseds */
        {"semiexpanded", 6},
        {"semiextended", 6},
        {"extraexpanded", 8},
        {"extraextended", 8},
        {"ultraexpanded", 9},
        {"ultraextended", 9},
        {"expanded", 7}, /* Must succeed other expandeds */
        {"extended", 7}, /* Must succeed other extendeds */
    };
    unsigned int i;
    int j;
    unsigned length;
    char *p;
    char FullName[128];

    if (h->seen[kWeightClass] && h->seen[kWidthClass]) {
        return;
    }

    /* Set defaults */
    if (!h->seen[kWeightClass]) {
        h->tbl.usWeightClass = FWEIGHT_NORMAL;
    }
    if (!h->seen[kWidthClass]) {
        h->tbl.usWidthClass = FWIDTH_NORMAL;
    }

    if (g->font.FullName == SID_UNDEF) {
        return; /* No FullName; assume default if no override */
    }
    /* Make null-terminated downcased copy without spaces */
    p = hotGetString(g, g->font.FullName, &length);
    i = 0;
    j = 0;
    while (j < 127 && i < length) {
        int c = p[i++];
        if (c != ' ') {
            FullName[j++] = isupper(c) ? tolower(c) : c;
        }
    }
    FullName[j] = '\0';

    /* Match weight class */
    if (!h->seen[kWeightClass]) {
        for (i = 0; i < ARRAY_LEN(weight); i++) {
            if (strstr(FullName, weight[i].string) != NULL) {
                /* so that we don't mistake 'ultracondensed' width for 'ultra' weight. */
                if ((0 == strcmp(weight[i].string, "ultra")) && (NULL != strstr(FullName, "ultrcondensed"))) {
                    continue;
                }
                h->tbl.usWeightClass = weight[i].value;
                break;
            }
        }
    }

    if (h->tbl.usWeightClass < 250) {
        hotMsg(h->g, hotERROR, "OS/2 weight class was set below 250 <%d>; bumping to  250. Use correct feature file override.", h->tbl.usWeightClass);
        h->tbl.usWeightClass = 250;
    }

    /* Match width class */
    if (!h->seen[kWidthClass]) {
        for (i = 0; i < ARRAY_LEN(width); i++) {
            if (strstr(FullName, width[i].string) != NULL) {
                h->tbl.usWidthClass = width[i].value;
                break;
            }
        }
    }
}

/* Set Panose number. We are really using the first 4 Panose bytes to convey
   information that was originally in the PitchAndFamily and CharSet fields of
   PFM or MMM files. The font driver has code that reads these Panose bytes
   and reconstitutes the original values. I'm not proud of this but there
   doesn't seem to be a better way of achieving this within the available sfnt
   tables. */
static void setPanose(hotCtx g, OS_2Ctx h) {
    /* Initialize */
    memset(h->tbl.panose, PANOSE_ANY, PANOSE_SIZE);

    /* Set family kind [0] and serif style [1] */
    switch (g->font.win.Family) {
        case HOT_ROMAN:
            h->tbl.panose[0] = PANOSE0_TEXT;
            h->tbl.panose[1] = PANOSE1_COVE;
            break;

        case HOT_SWISS:
            h->tbl.panose[0] = PANOSE0_TEXT;
            h->tbl.panose[1] = PANOSE1_SANS;
            break;

        case HOT_MODERN:
            h->tbl.panose[0] = PANOSE_NO_FIT;
            break;

        case HOT_SCRIPT:
            h->tbl.panose[0] = PANOSE0_SCRIPT;
            break;

        case HOT_DECORATIVE:
            /* xxx does Symbol font really need to be special? */
            h->tbl.panose[0] = strcmp(g->font.FontName.array, "Symbol") == 0 ? PANOSE0_PICTORIAL : PANOSE0_DECORATIVE;
            break;

        case HOT_DONTCARE:
            break;
    }

    /* Set Weight */
    h->tbl.panose[2] = h->tbl.usWeightClass / 100 + 1;

    /* Set Proportion */
    if (g->font.flags & FI_FIXED_PITCH) {
        h->tbl.panose[3] = PANOSE3_MONOSPACED;
    } else {
        switch (h->tbl.usWidthClass) {
            case 1:
            case 2:
                h->tbl.panose[3] = PANOSE3_VERY_CONDENSED;
                break;

            case 3:
            case 4:
                h->tbl.panose[3] = PANOSE3_CONDENSED;
                break;

            case 6:
            case 7:
                h->tbl.panose[3] = PANOSE3_EXPANDED;
                break;

            case 8:
            case 9:
                h->tbl.panose[3] = PANOSE3_VERY_EXPANDED;
                break;
        }
    }
}

/* Fail if fields haven't been set */
static void failIfNotSeen(OS_2Ctx h, int enumField, char *desc) {
    if (!h->seen[enumField]) {
        hotMsg(h->g, hotFATAL, "[internal] OS/2.%s not set", desc);
    }
}

int OS_2Fill(hotCtx g) {
    OS_2Ctx h = g->ctx.OS_2;
    FontInfo_ *font = &g->font;

    failIfNotSeen(h, kUnicodeRanges, "UnicodeRanges");
    failIfNotSeen(h, kCodePageRanges, "CodePageRanges");
    failIfNotSeen(h, kCharIndexRange, "CharIndexRange");

    h->tbl.xAvgCharWidth = font->win.AvgWidth;
    setClasses(g, h);
    if (!h->seen[kFSType]) {
        char *fsType = getenv(kFSTypeEnviron);
        if (fsType != NULL) {
            h->tbl.fsType = atoi(fsType);
        } else {
            h->tbl.fsType = EMBED_PRINT_AND_VIEW;
        }
    }
    h->tbl.ySubscriptXSize = font->win.SubscriptXSize;
    h->tbl.ySubscriptYSize = font->win.SubscriptYSize;
    h->tbl.ySubscriptXOffset = font->win.SubscriptXOffset;
    h->tbl.ySubscriptYOffset = font->win.SubscriptYOffset;

    h->tbl.ySuperscriptXSize = font->win.SuperscriptXSize;
    h->tbl.ySuperscriptYSize = font->win.SuperscriptYSize;
    h->tbl.ySuperscriptXOffset = font->win.SuperscriptXOffset;
    h->tbl.ySuperscriptYOffset = font->win.SuperscriptYOffset;

    h->tbl.yStrikeoutSize = font->win.StrikeOutSize;
    h->tbl.yStrikeoutPosition = font->win.StrikeOutPosition;

    if (!h->seen[kPanose]) {
        setPanose(g, h);
    }
    strncpy(h->tbl.achVendId, font->vendId, VEND_ID_SIZE);

    h->tbl.fsSelection = 0;
    if (font->flags & HOT_BOLD) {
        h->tbl.fsSelection |= SEL_BOLD;
    }
    if (font->flags & HOT_ITALIC) {
        h->tbl.fsSelection |= SEL_ITALIC;
    }
    if (h->tbl.fsSelection == 0 && h->tbl.usWeightClass == FWEIGHT_NORMAL) {
        h->tbl.fsSelection |= SEL_REGULAR;
    }

    if (g->font.fsSelectionMask_on >= 0) {
        h->tbl.fsSelection |= g->font.fsSelectionMask_on;
    }

    if (g->font.fsSelectionMask_off >= 0) {
        h->tbl.fsSelection &= ~g->font.fsSelectionMask_off;
    }

    if (h->tbl.usLowerOpticalPointSize > 0) {
        h->tbl.version = g->font.os2Version = 5;
    } else if (h->tbl.fsSelection >= 0x80) {
        /* If any of the OS/2 v 4 fsSelection bits are on.*/
        h->tbl.version = g->font.os2Version = 4;
    } else if ((h->tbl.ulUnicodeRange3 >= (uint32_t)(1 << 29)) || (h->tbl.ulUnicodeRange4 != 0)) {
        h->tbl.version = g->font.os2Version = 4;
    } else if (g->font.os2Version != 0) {
        h->tbl.version = g->font.os2Version;
    } else {
        h->tbl.version = 3;
    }

    h->tbl.sTypoAscender = font->TypoAscender;
    h->tbl.sTypoDescender = font->TypoDescender;
    h->tbl.sTypoLineGap = font->TypoLineGap;

    h->tbl.usWinAscent = font->win.ascent;
    h->tbl.usWinDescent = font->win.descent;

    /* Version 2 fields */
    h->tbl.sXHeight = font->win.XHeight;
    h->tbl.sCapHeight = font->win.CapHeight;
    h->tbl.usDefaultChar = font->win.DefaultChar;
    h->tbl.usBreakChar = font->win.BreakChar;

    return 1;
}

void OS_2Write(hotCtx g) {
    OS_2Ctx h = g->ctx.OS_2;
    OUT2(h->tbl.version);
    OUT2(h->tbl.xAvgCharWidth);
    OUT2(h->tbl.usWeightClass);
    OUT2(h->tbl.usWidthClass);
    OUT2(h->tbl.fsType);
    OUT2(h->tbl.ySubscriptXSize);
    OUT2(h->tbl.ySubscriptYSize);
    OUT2(h->tbl.ySubscriptXOffset);
    OUT2(h->tbl.ySubscriptYOffset);
    OUT2(h->tbl.ySuperscriptXSize);
    OUT2(h->tbl.ySuperscriptYSize);
    OUT2(h->tbl.ySuperscriptXOffset);
    OUT2(h->tbl.ySuperscriptYOffset);
    OUT2(h->tbl.yStrikeoutSize);
    OUT2(h->tbl.yStrikeoutPosition);
    OUT2(h->tbl.sFamilyClass);
    OUTN(PANOSE_SIZE, h->tbl.panose);
    OUT4(h->tbl.ulUnicodeRange1);
    OUT4(h->tbl.ulUnicodeRange2);
    OUT4(h->tbl.ulUnicodeRange3);
    OUT4(h->tbl.ulUnicodeRange4);
    OUTN(VEND_ID_SIZE, h->tbl.achVendId);
    OUT2(h->tbl.fsSelection);
    OUT2(h->tbl.usFirstCharIndex);
    OUT2(h->tbl.usLastCharIndex);
    OUT2(h->tbl.sTypoAscender);
    OUT2(h->tbl.sTypoDescender);
    OUT2(h->tbl.sTypoLineGap);
    OUT2(h->tbl.usWinAscent);
    OUT2(h->tbl.usWinDescent);
    OUT4(h->tbl.ulCodePageRange1);
    OUT4(h->tbl.ulCodePageRange2);
    OUT2(h->tbl.sXHeight);
    OUT2(h->tbl.sCapHeight);
    OUT2(h->tbl.usDefaultChar);
    OUT2(h->tbl.usBreakChar);
    OUT2(h->tbl.usMaxContext);
    if (h->tbl.version > 4) {
        OUT2(h->tbl.usLowerOpticalPointSize);
        OUT2(h->tbl.usUpperOpticalPointSize);
    }
}

void OS_2Reuse(hotCtx g) {
    OS_2Ctx h = g->ctx.OS_2;

    initFieldSeen(h);
    h->tbl.usMaxContext = 0;
    h->tbl.usLowerOpticalPointSize = 0;
    h->tbl.usUpperOpticalPointSize = 0;
}

void OS_2Free(hotCtx g) {
    MEM_FREE(g, g->ctx.OS_2);
}

/* ------------------------ Supplementary Functions ------------------------ */

void OS_2SetUnicodeRanges(hotCtx g,
                          uint32_t ulUnicodeRange1,
                          uint32_t ulUnicodeRange2,
                          uint32_t ulUnicodeRange3,
                          uint32_t ulUnicodeRange4) {
    OS_2Ctx h = g->ctx.OS_2;
    h->tbl.ulUnicodeRange1 = ulUnicodeRange1;
    h->tbl.ulUnicodeRange2 = ulUnicodeRange2;
    h->tbl.ulUnicodeRange3 = ulUnicodeRange3;
    h->tbl.ulUnicodeRange4 = ulUnicodeRange4;
    h->seen[kUnicodeRanges] = 1;
}

void OS_2SetCodePageRanges(hotCtx g,
                           uint32_t ulCodePageRange1,
                           uint32_t ulCodePageRange2) {
    OS_2Ctx h = g->ctx.OS_2;
    h->tbl.ulCodePageRange1 = ulCodePageRange1;
    h->tbl.ulCodePageRange2 = ulCodePageRange2;
    h->seen[kCodePageRanges] = 1;
}

void OS_2SetCharIndexRange(hotCtx g,
                           uint16_t usFirstCharIndex,
                           uint16_t usLastCharIndex) {
    OS_2Ctx h = g->ctx.OS_2;
    h->tbl.usFirstCharIndex = usFirstCharIndex;
    h->tbl.usLastCharIndex = usLastCharIndex;
    h->seen[kCharIndexRange] = 1;
}

/* May be called multiple times */
void OS_2SetMaxContext(hotCtx g, unsigned maxContext) {
    OS_2Ctx h = g->ctx.OS_2;
    h->tbl.usMaxContext = MAX(h->tbl.usMaxContext, maxContext);
    if (!h->seen[kMaxContext]) {
        h->seen[kMaxContext] = 1;
    }
}

void OS_2LowerOpticalPointSize(hotCtx g, uint16_t opSize) {
    OS_2Ctx h = g->ctx.OS_2;
    h->tbl.usLowerOpticalPointSize = opSize;
}

void OS_2UpperOpticalPointSize(hotCtx g, uint16_t opSize) {
    OS_2Ctx h = g->ctx.OS_2;
    h->tbl.usUpperOpticalPointSize = opSize;
}

void OS_2FamilyClass(hotCtx g, uint16_t familyClass) {
    OS_2Ctx h = g->ctx.OS_2;
    h->tbl.sFamilyClass = familyClass;
}

void OS_2SetPanose(hotCtx g, char *panose) {
    OS_2Ctx h = g->ctx.OS_2;
    memcpy(h->tbl.panose, panose, PANOSE_SIZE);
    h->seen[kPanose] = 1;
}

void OS_2SetFSType(hotCtx g, uint16_t fsType) {
    OS_2Ctx h = g->ctx.OS_2;
    h->tbl.fsType = fsType;
    h->seen[kFSType] = 1;
}

void OS_2SetWeightClass(hotCtx g, uint16_t weightClass) {
    OS_2Ctx h = g->ctx.OS_2;
    h->tbl.usWeightClass = weightClass;
    h->seen[kWeightClass] = 1;
}

void OS_2SetWidthClass(hotCtx g, uint16_t widthClass) {
    OS_2Ctx h = g->ctx.OS_2;
    h->tbl.usWidthClass = widthClass;
    h->seen[kWidthClass] = 1;
}
