#include "solvespace.h"

//-----------------------------------------------------------------------------
// Get the list of available font filenames, and load the name for each of
// them. Only that, though, not the glyphs too.
//-----------------------------------------------------------------------------
void TtfFontList::LoadAll(void) {
    if(loaded) return;

    // Get the list of font files from the platform-specific code.
    LoadAllFontFiles();

    int i;
    for(i = 0; i < l.n; i++) {
        TtfFont *tf = &(l.elem[i]);
        tf->LoadFontFromFile(true);
    }

    loaded = true;
}

void TtfFontList::PlotString(char *font, char *str, double spacing,
                             SBezierList *sbl,
                             Vector origin, Vector u, Vector v)
{
    LoadAll();

    int i;
    for(i = 0; i < l.n; i++) {
        TtfFont *tf = &(l.elem[i]);
        if(strcmp(tf->FontFileBaseName(), font)==0) {
            tf->LoadFontFromFile(false);
            tf->PlotString(str, spacing, sbl, origin, u, v);
            return;
        }
    }

    // Couldn't find the font; so draw a big X for an error marker.
    SBezier sb;
    sb = SBezier::From(origin, origin.Plus(u).Plus(v));
    sbl->l.Add(&sb);
    sb = SBezier::From(origin.Plus(v), origin.Plus(u));
    sbl->l.Add(&sb);
}


//=============================================================================

//-----------------------------------------------------------------------------
// Get a single character from the open .ttf file; EOF is an error, since
// we can always see that coming.
//-----------------------------------------------------------------------------
int TtfFont::Getc(void) {
    int c = fgetc(fh);
    if(c < 0) {
        throw "EOF";
    }
    return c;
}

//-----------------------------------------------------------------------------
// Helpers to get 1, 2, or 4 bytes from the .ttf file. Big endian.
//-----------------------------------------------------------------------------
int TtfFont::GetBYTE(void) {
    return Getc();
}
int TtfFont::GetWORD(void) {
    BYTE b0, b1;
    b1 = Getc();
    b0 = Getc();

    return (b1 << 8) | b0;
}
int TtfFont::GetDWORD(void) {
    BYTE b0, b1, b2, b3;
    b3 = Getc();
    b2 = Getc();
    b1 = Getc();
    b0 = Getc();

    return (b3 << 24) | (b2 << 16) | (b1 << 8) | b0;
}

//-----------------------------------------------------------------------------
// Load a glyph from the .ttf file into memory. Assumes that the .ttf file
// is already seeked to the correct location, and writes the result to
// glyphs[index]
//-----------------------------------------------------------------------------
void TtfFont::LoadGlyph(int index) {
    if(index < 0 || index >= glyphs) return;

    int i;

    SWORD contours          = GetWORD();
    SWORD xMin              = GetWORD();
    SWORD yMin              = GetWORD();
    SWORD xMax              = GetWORD();
    SWORD yMax              = GetWORD();

    if(useGlyph['A'] == index) {
        scale = (1024*1024) / yMax;
    }

    if(contours > 0) {
        WORD *endPointsOfContours =
            (WORD *)AllocTemporary(contours*sizeof(WORD));

        for(i = 0; i < contours; i++) {
            endPointsOfContours[i] = GetWORD();
        }
        WORD totalPts = endPointsOfContours[i-1] + 1;

        WORD instructionLength = GetWORD();
        for(i = 0; i < instructionLength; i++) {
            // We can ignore the instructions, since we're doing vector
            // output.
            (void)GetBYTE();
        }

        BYTE  *flags = (BYTE *)AllocTemporary(totalPts*sizeof(BYTE));
        SWORD *x     = (SWORD *)AllocTemporary(totalPts*sizeof(SWORD));
        SWORD *y     = (SWORD *)AllocTemporary(totalPts*sizeof(SWORD));

        // Flags, that indicate format of the coordinates
#define FLAG_ON_CURVE           (1 << 0)
#define FLAG_DX_IS_BYTE         (1 << 1)
#define FLAG_DY_IS_BYTE         (1 << 2)
#define FLAG_REPEAT             (1 << 3)
#define FLAG_X_IS_SAME          (1 << 4)
#define FLAG_X_IS_POSITIVE      (1 << 4)
#define FLAG_Y_IS_SAME          (1 << 5)
#define FLAG_Y_IS_POSITIVE      (1 << 5)
        for(i = 0; i < totalPts; i++) {
            flags[i] = GetBYTE();
            if(flags[i] & FLAG_REPEAT) {
                int n = GetBYTE();
                BYTE f = flags[i];
                int j;
                for(j = 0; j < n; j++) {
                    i++;
                    if(i >= totalPts) {
                        throw "too many points in glyph";
                    }
                    flags[i] = f;
                }
            }
        }

        // x coordinates
        SWORD xa = 0;
        for(i = 0; i < totalPts; i++) {
            if(flags[i] & FLAG_DX_IS_BYTE) {
                BYTE v = GetBYTE();
                if(flags[i] & FLAG_X_IS_POSITIVE) {
                    xa += v;
                } else {
                    xa -= v;
                }
            } else {
                if(flags[i] & FLAG_X_IS_SAME) {
                    // no change
                } else {
                    SWORD d = GetWORD();
                    xa += d;
                }
            }
            x[i] = xa;
        }

        // y coordinates
        SWORD ya = 0;
        for(i = 0; i < totalPts; i++) {
            if(flags[i] & FLAG_DY_IS_BYTE) {
                BYTE v = GetBYTE();
                if(flags[i] & FLAG_Y_IS_POSITIVE) {
                    ya += v;
                } else {
                    ya -= v;
                }
            } else {
                if(flags[i] & FLAG_Y_IS_SAME) {
                    // no change
                } else {
                    SWORD d = GetWORD();
                    ya += d;
                }
            }
            y[i] = ya;
        }
   
        Glyph *g = &(glyph[index]);
        g->pt = (FontPoint *)MemAlloc(totalPts*sizeof(FontPoint));
        int contour = 0;
        for(i = 0; i < totalPts; i++) {
            g->pt[i].x = x[i];
            g->pt[i].y = y[i];
            g->pt[i].onCurve = (BYTE)(flags[i] & FLAG_ON_CURVE);

            if(i == endPointsOfContours[contour]) {
                g->pt[i].lastInContour = true;
                contour++;
            } else {
                g->pt[i].lastInContour = false;
            }
        }
        g->pts = totalPts;
        g->xMax = xMax;
        g->xMin = xMin;

    } else {
        // This is a composite glyph, TODO.
    }
}

//-----------------------------------------------------------------------------
// Return the basename of our font filename; that's how the requests and
// entities that reference us will store it.
//-----------------------------------------------------------------------------
char *TtfFont::FontFileBaseName(void) {
    char *sb = strrchr(fontFile, '\\');
    char *sf = strrchr(fontFile, '/');
    char *s = sf ? sf : sb;
    if(!s) return "";
    return s + 1;
}

//-----------------------------------------------------------------------------
// Load a TrueType font into memory. We care about the curves that define
// the letter shapes, and about the mappings that determine which glyph goes
// with which character.
//-----------------------------------------------------------------------------
bool TtfFont::LoadFontFromFile(bool nameOnly) {
    if(loaded) return true;

    int i;
    
    fh = fopen(fontFile, "rb");
    if(!fh) {
        return false;
    }

    try {
        // First, load the Offset Table
        DWORD   version         = GetDWORD();
        WORD    numTables       = GetWORD();
        WORD    searchRange     = GetWORD();
        WORD    entrySelector   = GetWORD();
        WORD    rangeShift      = GetWORD();

        // Now load the Table Directory; our goal in doing this will be to
        // find the addresses of the tables that we will need.
        DWORD   glyfAddr = -1, glyfLen;
        DWORD   cmapAddr = -1, cmapLen;
        DWORD   headAddr = -1, headLen;
        DWORD   locaAddr = -1, locaLen;
        DWORD   maxpAddr = -1, maxpLen;
        DWORD   nameAddr = -1, nameLen;
        DWORD   hmtxAddr = -1, hmtxLen;
        DWORD   hheaAddr = -1, hheaLen;

        for(i = 0; i < numTables; i++) {
            char tag[5] = "xxxx";
            tag[0]              = GetBYTE();
            tag[1]              = GetBYTE();
            tag[2]              = GetBYTE();
            tag[3]              = GetBYTE();
            DWORD   checksum    = GetDWORD();
            DWORD   offset      = GetDWORD();
            DWORD   length      = GetDWORD();

            if(strcmp(tag, "glyf")==0) {
                glyfAddr = offset;
                glyfLen = length;
            } else if(strcmp(tag, "cmap")==0) {
                cmapAddr = offset;
                cmapLen = length;
            } else if(strcmp(tag, "head")==0) {
                headAddr = offset;
                headLen = length;
            } else if(strcmp(tag, "loca")==0) {
                locaAddr = offset;
                locaLen = length;
            } else if(strcmp(tag, "maxp")==0) {
                maxpAddr = offset;
                maxpLen = length;
            } else if(strcmp(tag, "name")==0) {
                nameAddr = offset;
                nameLen = length;
            } else if(strcmp(tag, "hhea")==0) {
                hheaAddr = offset;
                hheaLen = length;
            } else if(strcmp(tag, "hmtx")==0) {
                hmtxAddr = offset;
                hmtxLen = length;
            }
        }

        if(glyfAddr == -1 || cmapAddr == -1 || headAddr == -1 ||
           locaAddr == -1 || maxpAddr == -1 || hmtxAddr == -1 ||
           nameAddr == -1 || hheaAddr == -1)
        {
            throw "missing table addr";
        }

        // Load the name table. This gives us display names for the font, which
        // we need when we're giving the user a list to choose from.
        fseek(fh, nameAddr, SEEK_SET);

        WORD  nameFormat            = GetWORD();
        WORD  nameCount             = GetWORD();
        WORD  nameStringOffset      = GetWORD();
        // And now we're at the name records. Go through those till we find
        // one that we want.
        int displayNameOffset, displayNameLength;
        for(i = 0; i < nameCount; i++) {
            WORD    platformID      = GetWORD();
            WORD    encodingID      = GetWORD();
            WORD    languageID      = GetWORD();
            WORD    nameId          = GetWORD();
            WORD    length          = GetWORD();
            WORD    offset          = GetWORD();

            if(nameId == 4) {
                displayNameOffset = offset;
                displayNameLength = length;
                break;
            }
        }
        if(nameOnly && i >= nameCount) {
            throw "no name";
        }

        if(nameOnly) {
            // Find the display name, and store it in the provided buffer.
            fseek(fh, nameAddr+nameStringOffset+displayNameOffset, SEEK_SET);
            int c = 0;
            for(i = 0; i < displayNameLength; i++) {
                BYTE b = GetBYTE();
                if(b && c < (sizeof(name.str) - 2)) {
                    name.str[c++] = b;
                }
            }
            name.str[c++] = '\0';
         
            fclose(fh);
            return true;
        }


        // Load the head table; we need this to determine the format of the
        // loca table, 16- or 32-bit entries
        fseek(fh, headAddr, SEEK_SET);

        DWORD headVersion           = GetDWORD();
        DWORD headFontRevision      = GetDWORD();
        DWORD headCheckSumAdj       = GetDWORD();
        DWORD headMagicNumber       = GetDWORD();
        WORD  headFlags             = GetWORD();
        WORD  headUnitsPerEm        = GetWORD();
        (void)GetDWORD(); // created time
        (void)GetDWORD();
        (void)GetDWORD(); // modified time
        (void)GetDWORD();
        WORD  headXmin              = GetWORD();
        WORD  headYmin              = GetWORD();
        WORD  headXmax              = GetWORD();
        WORD  headYmax              = GetWORD();
        WORD  headMacStyle          = GetWORD();
        WORD  headLowestRecPPEM     = GetWORD();
        WORD  headFontDirectionHint = GetWORD();
        WORD  headIndexToLocFormat  = GetWORD();
        WORD  headGlyphDataFormat   = GetWORD();
        
        if(headMagicNumber != 0x5F0F3CF5) {
            throw "bad magic number";
        }

        // Load the hhea table, which contains the number of entries in the
        // horizontal metrics (hmtx) table.
        fseek(fh, hheaAddr, SEEK_SET);
        DWORD hheaVersion           = GetDWORD();
        WORD  hheaAscender          = GetWORD();
        WORD  hheaDescender         = GetWORD();
        WORD  hheaLineGap           = GetWORD();
        WORD  hheaAdvanceWidthMax   = GetWORD();
        WORD  hheaMinLsb            = GetWORD();
        WORD  hheaMinRsb            = GetWORD();
        WORD  hheaXMaxExtent        = GetWORD();
        WORD  hheaCaretSlopeRise    = GetWORD();
        WORD  hheaCaretSlopeRun     = GetWORD();
        WORD  hheaCaretOffset       = GetWORD();
        (void)GetWORD();
        (void)GetWORD();
        (void)GetWORD();
        (void)GetWORD();
        WORD  hheaMetricDataFormat  = GetWORD();
        WORD  hheaNumberOfMetrics   = GetWORD();

        // Load the maxp table, which determines (among other things) the number
        // of glyphs in the font
        fseek(fh, maxpAddr, SEEK_SET);

        DWORD maxpVersion               = GetDWORD();
        WORD  maxpNumGlyphs             = GetWORD();
        WORD  maxpMaxPoints             = GetWORD();
        WORD  maxpMaxContours           = GetWORD();
        WORD  maxpMaxComponentPoints    = GetWORD();
        WORD  maxpMaxComponentContours  = GetWORD();
        WORD  maxpMaxZones              = GetWORD();
        WORD  maxpMaxTwilightPoints     = GetWORD();
        WORD  maxpMaxStorage            = GetWORD();
        WORD  maxpMaxFunctionDefs       = GetWORD();
        WORD  maxpMaxInstructionDefs    = GetWORD();
        WORD  maxpMaxStackElements      = GetWORD();
        WORD  maxpMaxSizeOfInstructions = GetWORD();
        WORD  maxpMaxComponentElements  = GetWORD();
        WORD  maxpMaxComponentDepth     = GetWORD();

        glyphs = maxpNumGlyphs;
        glyph = (Glyph *)MemAlloc(glyphs*sizeof(glyph[0]));

        // Load the hmtx table, which gives the horizontal metrics (spacing
        // and advance width) of the font.
        fseek(fh, hmtxAddr, SEEK_SET);

        WORD  hmtxAdvanceWidth;
        SWORD hmtxLsb;
        for(i = 0; i < min(glyphs, hheaNumberOfMetrics); i++) {
            hmtxAdvanceWidth = GetWORD();
            hmtxLsb          = (SWORD)GetWORD();

            glyph[i].leftSideBearing = hmtxLsb;
            glyph[i].advanceWidth = hmtxAdvanceWidth;
        }
        // The last entry in the table applies to all subsequent glyphs also.
        for(; i < glyphs; i++) {
            glyph[i].leftSideBearing = hmtxLsb;
            glyph[i].advanceWidth = hmtxAdvanceWidth;
        }

        // Load the cmap table, which determines the mapping of characters to
        // glyphs.
        fseek(fh, cmapAddr, SEEK_SET);

        DWORD usedTableAddr = -1;

        WORD  cmapVersion        = GetWORD();
        WORD  cmapTableCount     = GetWORD();
        for(i = 0; i < cmapTableCount; i++) {
            WORD  platformId = GetWORD();
            WORD  encodingId = GetWORD();
            DWORD offset     = GetDWORD();

            if(platformId == 3 && encodingId == 1) {
                // The Windows Unicode mapping is our preference
                usedTableAddr = cmapAddr + offset;
            }
        }

        if(usedTableAddr == -1) {
            throw "no used table addr";
        }

        // So we can load the desired subtable; in this case, Windows Unicode,
        // which is us.
        fseek(fh, usedTableAddr, SEEK_SET);

        WORD  mapFormat             = GetWORD();
        WORD  mapLength             = GetWORD();
        WORD  mapVersion            = GetWORD();
        WORD  mapSegCountX2         = GetWORD();
        WORD  mapSearchRange        = GetWORD();
        WORD  mapEntrySelector      = GetWORD();
        WORD  mapRangeShift         = GetWORD();
        
        if(mapFormat != 4) {
            // Required to use format 4 per spec
            throw "not format 4";
        }

        int segCount = mapSegCountX2 / 2;
        WORD *endChar       = (WORD *)AllocTemporary(segCount*sizeof(WORD));
        WORD *startChar     = (WORD *)AllocTemporary(segCount*sizeof(WORD));
        WORD *idDelta       = (WORD *)AllocTemporary(segCount*sizeof(WORD));
        WORD *idRangeOffset = (WORD *)AllocTemporary(segCount*sizeof(WORD));

        DWORD *filePos = (DWORD *)AllocTemporary(segCount*sizeof(DWORD));

        for(i = 0; i < segCount; i++) {
            endChar[i] = GetWORD();
        }
        WORD  mapReservedPad        = GetWORD();
        for(i = 0; i < segCount; i++) {
            startChar[i] = GetWORD();
        }
        for(i = 0; i < segCount; i++) {
            idDelta[i] = GetWORD();
        }
        for(i = 0; i < segCount; i++) {
            filePos[i] = ftell(fh);
            idRangeOffset[i] = GetWORD();
        }

        // So first, null out the glyph table in our in-memory representation
        // of the font; any character for which cmap does not provide a glyph
        // corresponds to -1
        for(i = 0; i < arraylen(useGlyph); i++) {
            useGlyph[i] = 0;
        }

        for(i = 0; i < segCount; i++) {
            WORD v = idDelta[i];
            if(idRangeOffset[i] == 0) {
                int j;
                for(j = startChar[i]; j <= endChar[i]; j++) {
                    if(j > 0 && j < arraylen(useGlyph)) {
                        // Don't create a reference to a glyph that we won't
                        // store because it's bigger than the table.
                        if((WORD)(j + v) < glyphs) {
                            // Arithmetic is modulo 2^16
                            useGlyph[j] = (WORD)(j + v);
                        }
                    }
                }
            } else {
                int j;
                for(j = startChar[i]; j <= endChar[i]; j++) {
                    if(j > 0 && j < arraylen(useGlyph)) {
                        int fp = filePos[i];
                        fp += (j - startChar[i])*sizeof(WORD);
                        fp += idRangeOffset[i];
                        fseek(fh, fp, SEEK_SET);

                        useGlyph[j] = GetWORD();
                    }
                }
            }
        }

        // Load the loca table. This contains the offsets of each glyph,
        // relative to the beginning of the glyf table.
        fseek(fh, locaAddr, SEEK_SET);

        DWORD *glyphOffsets = (DWORD *)AllocTemporary(glyphs*sizeof(DWORD));

        for(i = 0; i < glyphs; i++) {
            if(headIndexToLocFormat == 1) {
                // long offsets, 32 bits
                glyphOffsets[i] = GetDWORD();
            } else if(headIndexToLocFormat == 0) {
                // short offsets, 16 bits but divided by 2
                glyphOffsets[i] = GetWORD()*2;
            } else {
                throw "bad headIndexToLocFormat";
            }
        }

        scale = 1024;
        // Load the glyf table. This contains the actual representations of the
        // letter forms, as piecewise linear or quadratic outlines.
        for(i = 0; i < glyphs; i++) {
            fseek(fh, glyfAddr + glyphOffsets[i], SEEK_SET);
            LoadGlyph(i);
        }
    } catch (char *s) {
        dbp("failed: '%s'", s);
        fclose(fh);
        return false;
    }

    fclose(fh);
    loaded = true;
    return true;
}

void TtfFont::Flush(void) {
    lastWas = NOTHING;
}

void TtfFont::Handle(int *dx, int x, int y, bool onCurve) {
    x = ((x + *dx)*scale + 512) >> 10;
    y = (y*scale + 512) >> 10;

    if(lastWas == ON_CURVE && onCurve) {
        // This is a line segment.
        LineSegment(lastOnCurve.x, lastOnCurve.y, x, y);
    } else if(lastWas == ON_CURVE && !onCurve) {
        // We can't do the Bezier until we get the next on-curve point,
        // but we must store the off-curve point.
    } else if(lastWas == OFF_CURVE && onCurve) {
        // We are ready to do a Bezier.
        Bezier(lastOnCurve.x, lastOnCurve.y, 
               lastOffCurve.x, lastOffCurve.y,
               x, y);
    } else if(lastWas == OFF_CURVE && !onCurve) {
        // Two consecutive off-curve points implicitly have an on-point
        // curve between them, and that should trigger us to generate a
        // Bezier.
        IntPoint fake;
        fake.x = (x + lastOffCurve.x) / 2;
        fake.y = (y + lastOffCurve.y) / 2;
        Bezier(lastOnCurve.x, lastOnCurve.y, 
               lastOffCurve.x, lastOffCurve.y,
               fake.x, fake.y);

        lastOnCurve.x = fake.x;
        lastOnCurve.y = fake.y;
    }

    if(onCurve) {
        lastOnCurve.x = x;
        lastOnCurve.y = y;
        lastWas = ON_CURVE;
    } else {
        lastOffCurve.x = x;
        lastOffCurve.y = y;
        lastWas = OFF_CURVE;
    }
}

void TtfFont::PlotCharacter(int *dx, int c, double spacing) {
    int gli = useGlyph[c];

    if(gli < 0 || gli >= glyphs) return;
    Glyph *g = &(glyph[gli]);
    if(!g->pt) return;

    if(c == ' ') {
        *dx += g->advanceWidth;
        return;
    }

    int dx0 = *dx;

    // A point that has x = xMin should be plotted at (dx0 + lsb); fix up
    // our x-position so that the curve-generating code will put stuff
    // at the right place.
    *dx = dx0 - g->xMin;
    *dx += g->leftSideBearing;

    int i;
    int firstInContour = 0;
    for(i = 0; i < g->pts; i++) {
        Handle(dx, g->pt[i].x, g->pt[i].y, g->pt[i].onCurve);
        
        if(g->pt[i].lastInContour) {
            int f = firstInContour;
            Handle(dx, g->pt[f].x, g->pt[f].y, g->pt[f].onCurve);
            firstInContour = i + 1;
            Flush();
        }
    }

    // And we're done, so advance our position by the requested advance
    // width, plus the user-requested extra advance.
    *dx = dx0 + g->advanceWidth + (int)(spacing + 0.5);
}

void TtfFont::PlotString(char *str, double spacing,
                         SBezierList *sbl,
                         Vector porigin, Vector pu, Vector pv)
{
    beziers = sbl;
    u = pu;
    v = pv;
    origin = porigin;

    if(!loaded || !str || *str == '\0') {
        LineSegment(0, 0, 1024, 0);
        LineSegment(1024, 0, 1024, 1024);
        LineSegment(1024, 1024, 0, 1024);
        LineSegment(0, 1024, 0, 0);
        return;
    }

    int dx = 0;

    while(*str) {
        PlotCharacter(&dx, *str, spacing);
        str++;
    }
}

Vector TtfFont::TransformIntPoint(int x, int y) {
    Vector r = origin;
    r = r.Plus(u.ScaledBy(x / 1024.0));
    r = r.Plus(v.ScaledBy(y / 1024.0));
    return r;
}

void TtfFont::LineSegment(int x0, int y0, int x1, int y1) {
    SBezier sb = SBezier::From(TransformIntPoint(x0, y0),
                               TransformIntPoint(x1, y1));
    beziers->l.Add(&sb);
}

void TtfFont::Bezier(int x0, int y0, int x1, int y1, int x2, int y2) {
    SBezier sb = SBezier::From(TransformIntPoint(x0, y0),
                               TransformIntPoint(x1, y1),
                               TransformIntPoint(x2, y2));
    beziers->l.Add(&sb);
}

