/*
Copyright (c) 2002-2003 by Juliusz Chroboczek

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
/* $XFree86: xc/programs/fonttosfnt/write.c,v 1.4tsi Exp $ */

#if defined(linux) && !defined(_GNU_SOURCE)
/* for fwrite_unlocked and fread_unlocked */
#define _GNU_SOURCE 1
#endif

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <netinet/in.h>
#include "X11/Xos.h"

#include "fonttosfnt.h"

#if !defined(I_LOVE_POSIX) && \
    defined(__GLIBC__) && __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 1
#define DO_FWRITE fwrite_unlocked
#define DO_FREAD fread_unlocked
#else
#define DO_FWRITE fwrite
#define DO_FREAD fread
#endif

static int writeDir(FILE*, FontPtr, int, unsigned*);
static int fixupDir(FILE*, FontPtr, int, int*, int*);
static int fixupChecksum(FILE*, int, int);

static int writeEBDT(FILE*, FontPtr);
static int writeEBLC(FILE*, FontPtr);
static int writeOS2(FILE*, FontPtr);
static int writePCLT(FILE*, FontPtr);
static int writecmap(FILE*, FontPtr);
static int writeglyf(FILE*, FontPtr);
static int writehead(FILE*, FontPtr);
static int writehhea(FILE*, FontPtr);
static int writehmtx(FILE*, FontPtr);
static int writeloca(FILE*, FontPtr);
static int writemaxp(FILE*, FontPtr);
static int writename(FILE*, FontPtr);
static int writepost(FILE*, FontPtr);

static CmapPtr current_cmap = NULL;
static int numglyphs, nummetrics;
static int write_error_occurred, read_error_occurred;

/* floor(log2(x)) */
static int
log2_floor(int x) 
{
    int i, j;

    if(x <= 0)
        abort();

    i = 0;
    j = 1;
    while(2 * j < x) {
        i++;
        j *= 2;
    }
    return i;
}

/* 2 ** floor(log2(x)) */
static int
two_log2_floor(int x)
{
    int j;
    
    if(x <= 0)
        abort();

    j = 1;
    while(2 * j < x) {
        j *= 2;
    }
    return j;
}

static void
write_error(int rc)
{
    /* Real Men program in C and don't use exceptions. */
    if(write_error_occurred)
        return;
    write_error_occurred = 1;
    if(rc < 0)
        perror("Couldn't write");
    else
        fprintf(stderr, "Short write.\n");
}

static void
read_error(int rc)
{
    if(read_error_occurred)
        return;
    read_error_occurred = 1;
    if(rc < 0)
        perror("Couldn't read");
    else
        fprintf(stderr, "Short read.\n");
}

static void
writeBYTE(FILE *out, unsigned char val)
{
    int rc;
    rc = DO_FWRITE(&val, 1, 1, out);
    if(rc != 1) write_error(rc);
}

static void
writeBYTEs(FILE *out, unsigned char *chars, int n)
{
    int rc;
    rc = DO_FWRITE(chars, 1, n, out);
    if(rc != n) write_error(rc);
}

static void
writeCHAR(FILE *out, char val)
{
    int rc;
    rc = DO_FWRITE(&val, 1, 1, out);
    if(rc != 1) write_error(rc);
}

static void
writeCHARs(FILE *out, char *chars, int n)
{
    int rc;
    rc = DO_FWRITE(chars, 1, n, out);
    if(rc != n) write_error(rc);
}

static void
writeUSHORT(FILE *out, unsigned short val)
{
    int rc;
    val = htons(val);
    rc = DO_FWRITE(&val, 2, 1, out);
    if(rc != 1) write_error(rc);
}

static void
writeSHORT(FILE *out, short val)
{
    int rc;
    val = htons(val);
    rc = DO_FWRITE(&val, 2, 1, out);
    if(rc != 1) write_error(rc);
}

static void
writeULONG(FILE *out, unsigned int val)
{
    int rc;
    val = htonl(val);
    rc = DO_FWRITE(&val, 4, 1, out);
    if(rc != 1) write_error(rc);
}

static void
writeLONG(FILE *out, int val)
{
    int rc;
    val = htonl(val);
    rc = DO_FWRITE(&val, 4, 1, out);
    if(rc != 1) write_error(rc);
}

static unsigned
readULONG(FILE *out)
{
    int rc;
    unsigned val;
    rc = DO_FREAD(&val, 4, 1, out);
    if(rc != 1) {
        read_error(rc);
        return 0xDEADBEEF;
    }
    return ntohl(val);
}

void
fontMetrics(FontPtr font)
{
    int i, rc;
    double sumAwidth = 0;
    unsigned count = 0;

    font->metrics.maxAwidth = 0;
    font->metrics.maxX = -10000 * TWO_SIXTEENTH;
    font->metrics.maxY = -10000 * TWO_SIXTEENTH;
    font->metrics.minX = 10000 * TWO_SIXTEENTH;
    font->metrics.minY = 10000 * TWO_SIXTEENTH;

    for(i = 0; i < FONT_CODES; i++) {
        int awidth, x0, y0, x1, y1;
        rc = glyphMetrics(font, i, &awidth, &x0, &y0, &x1, &y1);
        if(rc < 0)
            continue;

        if(awidth > font->metrics.maxAwidth) font->metrics.maxAwidth = awidth;
        if(x0 < font->metrics.minX) font->metrics.minX = x0;
        if(y0 < font->metrics.minY) font->metrics.minY = y0;
        if(x1 > font->metrics.maxX) font->metrics.maxX = x1;
        if(y1 > font->metrics.maxY) font->metrics.maxY = y1;

	if(awidth > 0) {
	    sumAwidth += awidth;
	    count++;
	}
    }

    if (count) font->metrics.awidth = sumAwidth / count;

    font->metrics.height = TWO_SIXTEENTH;

    if(font->pxMetrics.size == UNDEF) {
	font->pxMetrics.size = font->pxMetrics.height;
	font->metrics.size = font->metrics.height;
    }

    font->metrics.size = font->pxMetrics.size
	    * TWO_SIXTEENTH / font->pxMetrics.height;

    if(font->pxMetrics.ascent == UNDEF) {
	font->metrics.ascent = font->metrics.maxY;
	font->pxMetrics.ascent =
	    font->metrics.ascent
	    * font->pxMetrics.height / TWO_SIXTEENTH;
    }
    else
	font->metrics.ascent =
	    font->pxMetrics.ascent
	    * TWO_SIXTEENTH / font->pxMetrics.height;

    if(font->pxMetrics.descent == UNDEF) {
	font->metrics.descent = - font->metrics.minY;
	font->pxMetrics.descent =
	    font->metrics.descent
	    * font->pxMetrics.height / TWO_SIXTEENTH;
    }
    else
	font->metrics.descent =
	    font->pxMetrics.descent
	    * TWO_SIXTEENTH / font->pxMetrics.height;

    if(font->pxMetrics.capHeight == UNDEF) {
	if(glyphMetrics(font, 'X', NULL, NULL, NULL, NULL, &font->metrics.capHeight) != 1)
	    font->metrics.capHeight = font->metrics.ascent;
	font->pxMetrics.capHeight =
	    font->metrics.capHeight * font->pxMetrics.height / TWO_SIXTEENTH;
    }
    else
	font->metrics.capHeight =
	    font->pxMetrics.capHeight
	    * TWO_SIXTEENTH / font->pxMetrics.height;

    if(font->pxMetrics.xHeight == UNDEF) {
	if(glyphMetrics(font, 'x', NULL, NULL, NULL, NULL, &font->metrics.xHeight) != 1)
	    font->metrics.xHeight = font->metrics.capHeight * 2 / 3;
	font->pxMetrics.xHeight =
	    font->metrics.xHeight * font->pxMetrics.height / TWO_SIXTEENTH;
    }
    else
	font->metrics.xHeight =
	    font->pxMetrics.xHeight
	    * TWO_SIXTEENTH / font->pxMetrics.height;

    if(font->pxMetrics.underlinePosition == UNDEF)
	font->metrics.underlinePosition = - font->metrics.descent * 2;
    else {
	font->metrics.underlinePosition =
	    font->pxMetrics.underlinePosition
	    * TWO_SIXTEENTH / font->pxMetrics.height;
    }

    if(font->pxMetrics.underlineThickness == UNDEF)
	/* make sure thickness is at least one pixel. */
	/* TODO: this could be refined according to
	 * X Logical Font Description Conventions (xlfd.txt)
	 * by also considering the font weight. */
	font->metrics.underlineThickness =
	    TWO_SIXTEENTH
	    / (font->pxMetrics.height < 9 ? font->pxMetrics.height : 9);
    else
	font->metrics.underlineThickness =
	    font->pxMetrics.underlineThickness
	    * TWO_SIXTEENTH / font->pxMetrics.height;
}

int 
writeFile(char *filename, FontPtr font)
{
    int rc;
    FILE *out;
    unsigned tables[15];
    int head_position = 0;
    int full_length;
    int (*(table_writers[15]))(FILE*, FontPtr);
    int i, j;
    int offset[15], length[15];
    StrikePtr strike;

    out = fopen(filename, "wb+");
    if(out == NULL)
        return -1;

    current_cmap = makeCmap(font);
    if(current_cmap == NULL) {
        fprintf(stderr, "Couldn't build cmap.\n");
        goto fail;
    }

    fontMetrics(font);

    write_error_occurred = 0;
    read_error_occurred = 0;

    if(glyph_flag >= 2) {
        numglyphs = maxIndex(current_cmap) + 1;
        if(metrics_flag >= 2)
            nummetrics = numglyphs - 1;
        else if(metrics_flag >= 1)
            nummetrics = 1;
        else
            nummetrics = 0;
    } else if(glyph_flag == 1) {
        numglyphs = 1;
        nummetrics = (metrics_flag >= 1) ? 1 : 0;
    } else {
        numglyphs = 0;
        nummetrics = 0;
    }

    strike = font->strikes;
    while(strike) {
        strike->indexSubTables = makeIndexSubTables(strike, current_cmap);
        if(!strike->indexSubTables) {
            fprintf(stderr, "Couldn't build indexSubTable.\n");
            goto fail;
        }
        strike = strike->next;
    }

    /* These must be sorted lexicographically */
    i = 0;
    tables[i] = makeName("EBDT"); table_writers[i] = writeEBDT; i++;
    tables[i] = makeName("EBLC"); table_writers[i] = writeEBLC; i++;
    tables[i] = makeName("OS/2"); table_writers[i] = writeOS2; i++;
    tables[i] = makeName("PCLT"); table_writers[i] = writePCLT; i++;
    tables[i] = makeName("cmap"); table_writers[i] = writecmap; i++;
    if(numglyphs >= 1) {
        tables[i] = makeName("glyf");
        table_writers[i] = writeglyf; i++;
    }
    tables[i] = makeName("head"); table_writers[i] = writehead; i++;
    tables[i] = makeName("hhea"); table_writers[i] = writehhea; i++;
    if(nummetrics >= 1) {
        tables[i] = makeName("hmtx"); 
        table_writers[i] = writehmtx; i++;
    }
    if(numglyphs >= 1) {
        tables[i] = makeName("loca"); 
        table_writers[i] = writeloca; i++;
    }
    tables[i] = makeName("maxp"); table_writers[i] = writemaxp; i++;
    tables[i] = makeName("name"); table_writers[i] = writename; i++;
    tables[i] = makeName("post"); table_writers[i] = writepost; i++;

    rc = writeDir(out, font, i, tables);
    if(rc < 0)
        goto fail;

    for(j = 0; j < i; j++) {
        offset[j] = ftell(out);
        if(offset[j] < 0) {
            perror("Couldn't compute table offset");
            goto fail;
        }
        if(tables[j] == makeName("head"))
            head_position = offset[j];
        rc = table_writers[j](out, font);
        if(rc < 0 || write_error_occurred || read_error_occurred)
            goto fail;
        length[j] = ftell(out) - offset[j];
        if(length[j] < 0) {
            perror("Couldn't compute table size");
            goto fail;
        }
        if(length[j] % 4 != 0) {
            /* Pad -- recommended by the spec, and assumed by
               computeChecksum. */
            int k;
            for(k = 0; k < (4 - length[j] % 4); k++) {
                /* This must be 0 -- see computeChecksum. */
                writeBYTE(out, 0);
            }
            if(write_error_occurred || read_error_occurred)
                goto fail;
        }
    }

    rc = fixupDir(out, font, i, offset, length);
    if(rc < 0)
        goto fail;

    full_length = ftell(out);
    if(full_length < 0) {
        perror("Couldn't compute file size");
        goto fail;
    }
    while(full_length % 4 != 0) {
        /* pad for computeChecksum */
        writeBYTE(out, 0);
        full_length++;
    }
    if(write_error_occurred || read_error_occurred)
        goto fail;
    rc = fixupChecksum(out, full_length, head_position);
    if(rc < 0)
        goto fail;
    fclose(out);
    return 0;

 fail:
    fclose(out);
    unlink(filename);
    return -1;
}

static int
writeDir(FILE *out, FontPtr font, int numTables, unsigned *tables)
{
    int i, ti;
    i = 0; ti = 1;
    while(2 * ti < numTables) {
        i++;
        ti = 2 * ti;
    }

    writeULONG(out, 0x10000);   /* version */
    writeUSHORT(out, numTables); /* numTables */
    writeUSHORT(out, 16 * ti);  /* searchRange */
    writeUSHORT(out, i);        /* entrySelector */
    writeUSHORT(out, 16 * (numTables - ti)); /* rangeShift */

    /* see fixupDir */
    for(i = 0; i < numTables; i++) {
        writeULONG(out, tables[i]);
        writeULONG(out, 0xDEADFACE); /* checkSum */
        writeULONG(out, 0xDEADFACE); /* offset */
        writeULONG(out, 0xDEADFACE); /* length */
    }
    return 0;
}

static unsigned
computeChecksum(FILE *out, int offset, int length)
{
    int rc;
    int i;
    unsigned sum = 0;

    if(offset % 4 != 0) {
        fprintf(stderr, "Offset %d is not a multiple of 4\n", offset);
        return ~0;
    }

    rc = fseek(out, offset, SEEK_SET);
    if(rc < 0) {
        perror("Couldn't seek");
        return ~0;
    }

    /* This relies on the fact that we always pad tables with zeroes. */
    for(i = 0; i < length; i += 4) {
        sum += readULONG(out);
    }
    return sum;
}

static int
fixupDir(FILE *out, FontPtr font, int numTables, int *offset, int *length)
{
    int rc, i;
    unsigned sum;

    for(i = 0; i < numTables; i++) {
        sum = computeChecksum(out, offset[i], length[i]);
        rc = fseek(out, 12 + 16 * i + 4, SEEK_SET);
        if(rc != 0) {
            perror("Couldn't seek");
            return -1;
        }
        writeULONG(out, sum);
        writeULONG(out, offset[i]);
        writeULONG(out, length[i]);
    }
    return 0;
}

static int
fixupChecksum(FILE *out, int full_length, int head_position)
{
    int rc, checksum;
    checksum = computeChecksum(out, 0, full_length);
    rc = fseek(out, head_position + 8, SEEK_SET);
    if(rc != 0) {
        perror("Couldn't seek");
        return -1;
    }
    writeULONG(out, 0xB1B0AFBA - checksum); /* checkSumAdjustment */
    return 0;
}

    
static int 
writehead(FILE* out, FontPtr font)
{
    int time_hi = 0;
    unsigned time_lo = 0;

    macTime(&time_hi, &time_lo);

    writeULONG(out, 0x00010000);
    writeULONG(out, 0x00010000); /* fontRevision */
    writeULONG(out, 0);         /* checkSumAdjustment -- filled in later */
    writeULONG(out,0x5F0F3CF5); /* magicNumber */
    writeUSHORT(out, 1);        /* flags */
    writeUSHORT(out, UNITS_PER_EM); /* unitsPerEm */

    writeLONG(out, time_hi);    /* created */
    writeULONG(out, time_lo);
    writeLONG(out, time_hi);    /* modified */
    writeULONG(out, time_lo);

    /* bounding box for all glyphs */
    writeUSHORT(out, FONT_UNITS_FLOOR(font->metrics.minX));
    writeUSHORT(out, FONT_UNITS_FLOOR(font->metrics.minY));
    writeUSHORT(out, FONT_UNITS_CEIL(font->metrics.maxX));
    writeUSHORT(out, FONT_UNITS_CEIL(font->metrics.maxY));

    writeUSHORT(out, font->flags); /* macStyle */
    writeUSHORT(out, 1);        /* lowestRecPPEM */
    writeSHORT(out, 0);         /* fontDirectionHint */
    writeSHORT(out, 0);         /* indexToLocFormat */
    writeSHORT(out, 0);         /* glyphDataFormat */
    return 0;
}

static int
outputRaster(FILE *out, char *raster, int width, int height, int stride,
             int bit_aligned)
{
    int i, j;
    int len = 0;

    if(!bit_aligned || width % 8 == 0) {
        for(i = 0; i < height; i++) {
            writeCHARs(out, raster + i * stride, (width + 7) / 8);
            len += (width + 7) / 8;
        }
    } else {
        int bit = 0;
        unsigned char v = 0;
        for(i = 0; i < height; i++) {
            for(j = 0; j < width; j++) {
                if(BITREF(raster, stride, j, i))
                    v |= 1 << (7 - bit);
                bit++;
                if(bit >= 8) {
                    writeBYTE(out, v);
                    len++;
                    bit = 0;
                    v = 0;
                }
            }
        }
        if(bit > 0) {
            writeBYTE(out, v);
            len++;
        }
    }
    return len;
}

static int 
writeEBDT(FILE* out, FontPtr font)
{
    StrikePtr strike;
    BitmapPtr bitmap;
    IndexSubTablePtr table;
    int i;
    int offset;
    int ebdt_start;

    ebdt_start = ftell(out);

    writeULONG(out, 0x00020000); /* version */
    offset = 4;

    strike = font->strikes;
    while(strike) {
        table = strike->indexSubTables;
        while(table) {
            for(i = table->firstGlyphIndex; i <= table->lastGlyphIndex; i++) {
                bitmap = strikeBitmapIndex(strike, current_cmap, i);
                bitmap->location = offset;
                if(bit_aligned_flag && table->constantMetrics) {
                    /* image format 5 */
                    ;
                } else {
                    /* image format 1 or 2 */
                    writeBYTE(out, bitmap->height);
                    writeBYTE(out, bitmap->width);
                    writeCHAR(out, bitmap->horiBearingX);
                    writeCHAR(out, bitmap->horiBearingY);
                    writeBYTE(out, bitmap->advanceWidth);
                    offset += 5;
                }
                offset += outputRaster(out, 
                                       bitmap->raster, 
                                       bitmap->width, bitmap->height,
                                       bitmap->stride,
                                       bit_aligned_flag);
            }
            table->lastLocation = offset;
            table = table->next;
        }
        strike = strike->next;
    }
    if(ftell(out) != ebdt_start + offset)
        abort();
    return 0;
}

static int 
writeEBLC(FILE* out, FontPtr font)
{
    int i, rc, numstrikes, eblc_start, num, den;
    StrikePtr strike;
    IndexSubTablePtr table;

    degreesToFraction(font->italicAngle, &num, &den);

    numstrikes = 0;
    strike = font->strikes;
    while(strike) {
        numstrikes++;
        strike = strike->next;
    }

    eblc_start = ftell(out);
    
    writeULONG(out, 0x00020000); /* version */
    writeULONG(out, numstrikes); /* numSizes */

    /* bitmapSizeTable */
    strike = font->strikes;
    while(strike) {
        strike->bitmapSizeTableLocation = ftell(out);
        writeULONG(out, 0xDEADFACE); /* indexSubTableArrayOffset */
        writeULONG(out, 0xDEADFACE); /* indexTablesSize */
        writeULONG(out, 0xDEADFACE); /* numberOfIndexSubTables */
        writeULONG(out, 0);     /* colorRef */
	for (i = 0; i <= 1; i++) {
	  writeCHAR(out, font->pxMetrics.ascent);	/* ascender */
	  writeCHAR(out, -font->pxMetrics.descent);	/* descender */
	  writeBYTE(out, strikeMaxWidth(strike));	/* widthMax */
	  writeCHAR(out, num);				/* caretSlopeNumerator */
	  writeCHAR(out, den);				/* caretSlopeDenominator */
	  writeCHAR(out, 0);				/* caretOffset */
	  writeCHAR(out, 0);				/* minOriginSB */
	  writeCHAR(out, 0);				/* minAdvanceSB */
	  writeCHAR(out, 0);				/* maxBeforeBL */
	  writeCHAR(out, 0);				/* minAfterBL */
	  writeCHAR(out, 0);				/* pad1 */
	  writeCHAR(out, 0);				/* pad2 */
	}
        writeUSHORT(out, 0);    /* startGlyphIndex */
        writeUSHORT(out, 0xFFFD); /* endGlyphIndex */
        writeBYTE(out, strike->sizeX); /* ppemX */
        writeBYTE(out, strike->sizeY); /* ppemY */
        writeBYTE(out, 1);      /* bitDepth */
        writeCHAR(out, 1);      /* flags */
        strike = strike->next;
    }

    /* indexSubTableArray, one per strike */
    strike = font->strikes;
    while(strike) {
        int endoffset;
        int numtables = 0;

        strike->indexSubTableLocation = ftell(out);
        table = strike->indexSubTables;
        while(table) {
            table->location = ftell(out);
            writeUSHORT(out, table->firstGlyphIndex);
            writeUSHORT(out, table->lastGlyphIndex);
            writeULONG(out, 0xDEADFACE); /* additionalOffsetToIndexSubtable */
            numtables++;
            table = table->next;
        }
        endoffset = ftell(out);
        rc = fseek(out, strike->bitmapSizeTableLocation, SEEK_SET);
        if(rc != 0) {
            perror("Couldn't seek");
            return -1;
        }
        writeULONG(out, strike->indexSubTableLocation - eblc_start); 
                                              /* indexSubTableArrayOffset */
        writeULONG(out, endoffset - strike->indexSubTableLocation);
                                              /* indexTablesSize */
        writeULONG(out, numtables);           /* numberOfIndexSubTables */
        rc = fseek(out, endoffset, SEEK_SET);
        if(rc != 0) {
            perror("Couldn't seek");
            return -1;
        }
        strike = strike->next;
    }

    /* actual indexSubTables */
    strike = font->strikes;
    while(strike) {
        table = strike->indexSubTables;
        while(table) {
            int location;
            int data_location;
            int short_offsets;
            int offset;

            location = ftell(out);
            rc = fseek(out, table->location + 4, SEEK_SET);
            if(rc != 0) {
                perror("Couldn't seek");
                return -1;
            }
            /* additionalOffsetToIndexSubtable */
            writeULONG(out, location - strike->indexSubTableLocation);
            rc = fseek(out, location, SEEK_SET);
            if(rc != 0) {
                perror("Couldn't seek");
                return -1;
            }
            data_location =
                strikeBitmapIndex(strike, current_cmap,
                                  table->firstGlyphIndex)->location;
            short_offsets = 1;
            for(i = table->firstGlyphIndex; i <= table->lastGlyphIndex; i++) {
                if(strikeBitmapIndex(strike, current_cmap, i)->location -
                   data_location > 0xFFFF) {
                    short_offsets = 0;
                    break;
                }
            }
            /* indexFormat */
            if(table->constantMetrics)
                writeUSHORT(out, 2);
            else if(short_offsets)
                writeUSHORT(out, 3);
            else
                writeUSHORT(out, 1);
            /* imageFormat */
            if(bit_aligned_flag) {
                if(table->constantMetrics)
                    writeUSHORT(out, 5);
                else
                    writeUSHORT(out, 2);
            } else {
                writeUSHORT(out, 1);
            }
            writeULONG(out, data_location);
            if(table->constantMetrics) {
                int size;
                BitmapPtr bitmap = 
                    strikeBitmapIndex(strike, current_cmap, 
                                      table->firstGlyphIndex);

                size = 
                    strikeBitmapIndex(strike, current_cmap, 
                                      table->firstGlyphIndex + 1)->location -
                    bitmap->location;
                writeULONG(out, size); /* imageSize */
                /* bigMetrics */
                writeBYTE(out, bitmap->height);
                writeBYTE(out, bitmap->width);
                writeCHAR(out, bitmap->horiBearingX);
                writeCHAR(out, bitmap->horiBearingY);
                writeBYTE(out, bitmap->advanceWidth);
                writeCHAR(out, bitmap->horiBearingX); /* vertBearingX */
                writeCHAR(out, bitmap->horiBearingY); /* vertBearingY */
                writeBYTE(out, font->metrics.maxAwidth); /* vertAdvance */
            } else {
                for(i = table->firstGlyphIndex; 
                    i <= table->lastGlyphIndex; i++) {
                    offset = 
                        strikeBitmapIndex(strike, current_cmap, i)->location -
                        data_location;
                    if(short_offsets)
                        writeUSHORT(out, offset);
                    else
                        writeULONG(out, offset);
                }
                /* Dummy glyph of size 0 to mark the end of the table */
                if(short_offsets) {
                    writeUSHORT(out, table->lastLocation - data_location);
                    writeUSHORT(out, table->lastLocation - data_location);
                } else {
                    writeULONG(out, table->lastLocation - data_location);
                    writeULONG(out, table->lastLocation - data_location);
                }
            }
            location = ftell(out);
            while(location % 4 != 0) {
                writeCHAR(out, 0);
                location--;
            }
            table = table->next;
        }
        strike = strike->next;
    }
    return 0;
}

static int 
writecmap(FILE* out, FontPtr font)
{
    int rc, cmap_start, cmap_end;
    CmapPtr cmap;
    int segcount;

    segcount = 0;
    cmap = current_cmap;
    while(cmap) {
        segcount++;
        cmap = cmap->next;
    }

    segcount++;                 /* dummy segment to end table */

    cmap_start = ftell(out);

    writeUSHORT(out, 0);        /* version */
    writeUSHORT(out, 1);        /* number of encoding tables */
    writeUSHORT(out, 3);        /* platform ID */
    writeUSHORT(out, (font->flags & FACE_SYMBOL) ? 0 : 1);
                                /* encoding ID */
    writeULONG(out, 12);        /* offset to beginning of subtable */

    /* subtable */
    writeUSHORT(out, 4);        /* format */
    writeUSHORT(out, 0xDEAD);   /* length */
    writeUSHORT(out, 0);        /* language */
    /* How baroque can you get? */
    writeUSHORT(out, segcount * 2); /* segCountX2 */
    writeUSHORT(out, 2 * two_log2_floor(segcount)); /* searchRange */
    writeUSHORT(out, 1 + log2_floor(segcount));   /* entrySelector */
    writeUSHORT(out, 2 * (segcount - two_log2_floor(segcount)));   
                                /* rangeShift */

    cmap = current_cmap;
    while(cmap) {
        writeUSHORT(out, cmap->endCode);
        cmap = cmap->next;
    }
    writeUSHORT(out, 0xFFFF);

    writeUSHORT(out, 0);        /* reservedPad */

    cmap = current_cmap;
    while(cmap) {
        writeUSHORT(out, cmap->startCode);
        cmap = cmap->next;
    }
    writeUSHORT(out, 0xFFFF);

    /* idDelta */
    cmap = current_cmap;
    while(cmap) {
        writeUSHORT(out, (cmap->index - cmap->startCode) & 0xFFFF);
        cmap = cmap->next;
    }
    writeUSHORT(out, 1);

    /* idRangeOffset */
    cmap = current_cmap;
    while(cmap) {
        writeUSHORT(out, 0);
        cmap = cmap->next;
    }
    writeUSHORT(out, 0);

    /* glyphIDArray is empty */

    cmap_end = ftell(out);
    rc = fseek(out, cmap_start + 12 + 2, SEEK_SET);
    if(rc != 0) {
        perror("Couldn't seek");
        return -1;
    }
    writeUSHORT(out, cmap_end - cmap_start - 12); /* length */
    rc = fseek(out, cmap_end, SEEK_SET);
    if(rc != 0) {
        perror("Couldn't seek");
        return -1;
    }
    return 0;
}

static int 
writeglyf(FILE* out, FontPtr font)
{
    return 0;
}

int
writehhea(FILE* out, FontPtr font)
{
    int num, den;
    degreesToFraction(font->italicAngle, &num, &den);

    writeULONG(out, 0x00010000); /* version */
    writeSHORT(out, FONT_UNITS_CEIL(font->metrics.ascent)); /* ascender */
    writeSHORT(out, -FONT_UNITS_CEIL(font->metrics.descent)); /* descender */
    writeSHORT(out, FONT_UNITS(font->metrics.size - font->metrics.ascent - font->metrics.descent));	/* lineGap */
    writeUSHORT(out, FONT_UNITS(font->metrics.maxAwidth)); /* advanceWidthMax */
    /* TODO: the next three are not calculated according to spec, are they ?
     * https://docs.microsoft.com/en-us/typography/opentype/spec/hhea */
    writeSHORT(out, FONT_UNITS_FLOOR(font->metrics.minX)); /* minLeftSideBearing */
    writeSHORT(out, FONT_UNITS_FLOOR(font->metrics.minX)); /* minRightSideBearing */
    writeSHORT(out, FONT_UNITS_CEIL(font->metrics.maxX)); /* xMaxExtent */
    writeSHORT(out, den);       /* caretSlopeRise */
    writeSHORT(out, num);       /* caretSlopeRun */
    writeSHORT(out, 0);         /* reserved */
    writeSHORT(out, 0);         /* reserved */
    writeSHORT(out, 0);         /* reserved */
    writeSHORT(out, 0);         /* reserved */
    writeSHORT(out, 0);         /* reserved */
    writeSHORT(out, 0);         /* metricDataFormat */
    writeSHORT(out, nummetrics); /* numberOfHMetrics */
    return 0;
}

static int 
writehmtx(FILE* out, FontPtr font)
{
    int rc, i;

    for(i = 0; i <= numglyphs; i++) {
        int code, width, lsb;
        code = findCode(current_cmap, i);
        if(code < 0)
            rc = -1;
        else
            rc = glyphMetrics(font, code, &width, &lsb, NULL, NULL, NULL);
        if(rc < 0) {
            width = UNITS_PER_EM / 3;
            lsb = 0;
        }
        if(i < nummetrics) {
            writeSHORT(out, FONT_UNITS(width));
            writeSHORT(out, FONT_UNITS(lsb));
        } else {
            writeSHORT(out, FONT_UNITS(lsb));
        }
    }
    return 0;
}

static int 
writeloca(FILE* out, FontPtr font)
{
    int i;

    /* All glyphs undefined -- loca table is empty, so offset 0 */
    for(i = 0; i < numglyphs; i++) {
        writeSHORT(out, 0);
    }
    writeSHORT(out, 0);
    return 0;
}

static int 
writemaxp(FILE* out, FontPtr font)
{
    writeLONG(out, 0x00010000); /* version */
    writeUSHORT(out, numglyphs); /* numGlyphs */
    writeUSHORT(out, 0);        /* maxPoints */
    writeUSHORT(out, 0);        /* maxContours */
    writeUSHORT(out, 0);        /* maxCompositePoints */
    writeUSHORT(out, 0);        /* maxCompositeContours */
    writeUSHORT(out, 1);        /* maxZones */
    writeUSHORT(out, 0);        /* maxTwilightPoints */
    writeUSHORT(out, 0);        /* maxStorage */
    writeUSHORT(out, 0);        /* maxFunctionDefs */
    writeUSHORT(out, 0);        /* maxInstructionDefs */
    writeUSHORT(out, 0);        /* maxStackElements */
    writeUSHORT(out, 0);        /* maxSizeOfInstructions */
    writeUSHORT(out, 0);        /* maxComponentElements */
    writeUSHORT(out, 0);        /* maxComponentDepth */
    return 0;
}

static int 
writename(FILE* out, FontPtr font)
{
    int i;
    int offset;

    writeUSHORT(out, 0);        /* format selector */
    writeUSHORT(out, font->numNames);
    writeUSHORT(out, 6 + font->numNames * 12); /* offset to string storage */
    offset = 0;
    for(i = 0; i < font->numNames; i++) {
        writeUSHORT(out, 3);    /* platform id -- Microsoft */
        writeUSHORT(out, 1);    /* encoding -- Unicode */
        writeUSHORT(out, 0x409); /* language id -- American English */
        writeUSHORT(out, font->names[i].nid); /* name id */
        writeUSHORT(out, font->names[i].size); /* string length */
        writeUSHORT(out, offset); /* string offset */
        offset += font->names[i].size;
    }
    for(i = 0; i < font->numNames; i++)
        writeCHARs(out, font->names[i].value, font->names[i].size);
    return 0;
}

static int 
writepost(FILE* out, FontPtr font)
{
    int i, rc, previous_width, width, fixed_pitch;

    fixed_pitch = 1;
    previous_width = -1;
    for(i = 0; i < FONT_CODES; i++) {
        rc = glyphMetrics(font, i, &width, NULL, NULL, NULL, NULL);
        if(rc < 0)
            continue;
        if(previous_width >= 0) {
            if(width != previous_width) {
                fixed_pitch = 0;
                break;
            }
        }
        previous_width = width;
    }
    
    writeULONG(out, 0x00030000); /* FormatType */
    writeULONG(out, font->italicAngle); /* italicAngle */
    writeSHORT(out, FONT_UNITS(font->metrics.underlinePosition));
    writeSHORT(out, FONT_UNITS(font->metrics.underlineThickness));
    writeULONG(out, fixed_pitch); /* isFixedPitch */
    writeULONG(out, 0);         /* minMemType42 */
    writeULONG(out, 0);         /* maxMemType42 */
    writeULONG(out, 0);         /* minMemType1 */
    writeULONG(out, 0);         /* maxMemType1 */
    return 0;
}

static int 
writeOS2(FILE* out, FontPtr font)
{
    int i;

    writeUSHORT(out, 5); /* version */
    writeSHORT(out, FONT_UNITS(font->metrics.awidth)); /* xAvgCharWidth; */
    writeUSHORT(out, font->weight);  /* usWeightClass; */
    writeUSHORT(out, font->width); /* usWidthClass; */
    writeSHORT(out, 0);         /* fsType; */
    writeSHORT(out, UNITS_PER_EM / 5); /* ySubscriptXSize; */
    writeSHORT(out, UNITS_PER_EM / 5); /* ySubscriptYSize; */
    writeSHORT(out, 0);         /* ySubscriptXOffset; */
    writeSHORT(out, UNITS_PER_EM / 5); /* ySubscriptYOffset; */
    writeSHORT(out, UNITS_PER_EM / 5); /* ySuperscriptXSize; */
    writeSHORT(out, UNITS_PER_EM / 5); /* ySuperscriptYSize; */
    writeSHORT(out, 0);         /* ySuperscriptXOffset; */
    writeSHORT(out, UNITS_PER_EM / 5); /* ySuperscriptYOffset; */
    writeSHORT(out, FONT_UNITS(font->metrics.underlineThickness));
    /* yStrikeoutSize; */
    writeSHORT(out, UNITS_PER_EM / 4); /* yStrikeoutPosition; */
    writeSHORT(out, 0);         /* sFamilyClass; */
    for(i = 0; i < 10; i++)
        writeBYTE(out, 0);      /* panose; */
    writeULONG(out, 0xFFFF);    /* ulUnicodeRange1; */
    writeULONG(out, 0xFFFF);    /* ulUnicodeRange2; */
    writeULONG(out, 0x03FF);    /* ulUnicodeRange3; */
    writeULONG(out, 0U);        /* ulUnicodeRange4; */
    writeULONG(out, font->foundry); /* achVendID[4]; */
    i = 0;
    if (font->flags & FACE_ITALIC)
	i |= 1 << 0;
    if (font->flags & FACE_BOLD)
	i |= 1 << 5;
    if (!i)
	i |= 1 << 6;
#ifndef NO_TYPO_METRICS
    i |= 1 << 7; /* USE_TYPO_METRICS instead usWin metrics for line spacing. */
#endif
    writeUSHORT(out, i);	/* fsSelection; */
    writeUSHORT(out, 0x20);     /* usFirstCharIndex; */
    writeUSHORT(out, 0xFFFD);   /* usLastCharIndex; */
    writeUSHORT(out, FONT_UNITS_CEIL(font->metrics.ascent)); /* sTypoAscender; */
    writeSHORT(out, -FONT_UNITS_CEIL(font->metrics.descent)); /* sTypoDescender; */
    writeSHORT(out, FONT_UNITS(font->metrics.size - font->metrics.ascent - font->metrics.descent));	/* sTypoLineGap */
#ifdef NO_TYPO_METRICS
    writeUSHORT(out, FONT_UNITS_CEIL(font->metrics.ascent)); /* usWinAscent; */
    writeUSHORT(out, FONT_UNITS_CEIL(font->metrics.descent)); /* usWinDescent; */
#else
    writeUSHORT(out, FONT_UNITS_CEIL(font->metrics.maxY)); /* usWinAscent; */
    writeUSHORT(out, -FONT_UNITS_FLOOR(font->metrics.minY)); /* usWinDescent; */
#endif
    writeULONG(out, 3);						/* ulCodePageRange1; */
    writeULONG(out, 0);         				/* ulCodePageRange2; */
    writeSHORT(out, FONT_UNITS_CEIL(font->metrics.xHeight));	/* sxHeight; */
    writeSHORT(out, FONT_UNITS_CEIL(font->metrics.capHeight));	/* sCapHeight; */
    writeUSHORT(out, 0); 					/* usDefaultChar; */
    writeUSHORT(out, 20); 					/* usBreakChar; */
    writeUSHORT(out, 0); 					/* usMaxContext; */
    writeUSHORT(out, 0); 					/* usLowerOpticalPointSize; */
    writeUSHORT(out, 0xffff); 					/* usUpperOpticalPointSize; */
    return 0;
}

static int 
writePCLT(FILE* out, FontPtr font)
{
    char name[16] = "X11 font        ";
    char filename[6] = "X11R00";
    unsigned char charComplement[8] = 
        {0xFF, 0xFF, 0xFF, 0xFF, 0x0B, 0xFF, 0xFF, 0xFE};
    int style, w, strokeWeight, widthType;

    style = 0;
    if(font->flags & FACE_ITALIC)
        style = 1;

    w = (font->weight + 50) / 100;
    if(w < 5)
        strokeWeight = w - 6;
    else if(w == 5)
        strokeWeight = 0;
    else
        strokeWeight = w - 4;

    if(font->width <= 2)
        widthType = -3;
    else if(font->width <= 4)
        widthType = -2;
    else if(font->width <= 6)
        widthType = 0;
    else if(font->width <= 7)
        widthType = 2;
    else
        widthType = 3;

    writeULONG(out, 0x00010000); /* version */
    writeULONG(out, 0);         /* FontNumber */
    writeUSHORT(out, FONT_UNITS(font->metrics.maxAwidth)); /* pitch */
    writeUSHORT(out, FONT_UNITS(font->metrics.xHeight));    /* xHeight */
    writeUSHORT(out, style);    /* style */
    writeUSHORT(out, 6 << 12);  /* TypeFamily */
    writeUSHORT(out, FONT_UNITS(font->metrics.xHeight)); /* CapHeight */
    writeUSHORT(out, 0);        /* SymbolSet */
    writeCHARs(out, name, 16);  /* TypeFace */
    writeBYTEs(out, charComplement, 8); /* CharacterComplement */
    writeCHARs(out, filename, 6); /* FileName */
    writeCHAR(out, strokeWeight); /* StrokeWeight */
    writeCHAR(out, widthType);  /* WidthType */
    writeCHAR(out, 1 << 6);     /* SerifStyle */
    writeCHAR(out, 0);          /* Reserved */
    return 0;
}
