/* char_routines.c: Data structures for character information

This file is part of the Omega project, which
is based on the web2c distribution of TeX.

Copyright (C) 2000 John Plaice and Yannis Haralambous

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Library General Public License for more details.

You should have received a copy of the GNU Library General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */

#include "cpascal.h"
#include "manifests.h"
#include "list_routines.h"
#include "char_routines.h"
#include "print_routines.h"
#include "out_routines.h"
#include "error_routines.h"
#include "ligkern_routines.h"
#include "dimen_routines.h"
#include "header_routines.h"
#include "font_routines.h"

#define PLANE		0x10000
#define HALFPLANE	0x08000

#define MAX_START	65535

/*
 * Characters can range from 0x0 to 0x7fffffff (31 bits unsigned),
 * which is a lot of characters.  We would expect characters to either
 * be bunched up in a given region, or else sparsely defined throughout
 * the range.  The data structure to hold them is an array of HALFPLANE
 * arrays, each of which holds a full PLANE (0x10000) of characters.
 *
 * At all times, init_character ensures that planes[0] to planes[plane_max]
 * are either NULL or allocated arrays of PLANE characters.
 *
 * For allocated array p, init_character also ensures that planes[p][0]
 * to planes[p][char_max[p]] are either NULL or pointers to allocated
 * char_entry values.
 *
 * init_character can be called when actually reading a CHARACTER definition
 * or when the character is referenced in a charlist cycle, an extensible
 * piece or in a ligature/kerning table.  In the latter case, ensure_existence
 * is called, and it sets the defined field of the character to be FALSE;
 * 
 * init_character also sets current_character to the character entry;
 * 
 */

unsigned bc=0x7fffffff;
unsigned ec=0x0;
unsigned ne=0;

char_entry **planes[HALFPLANE];
unsigned char_max[HALFPLANE];
unsigned plane_max = 0;

char_entry *current_character = NULL;

queue exten_queue;
four_pieces **exten_table = NULL;

unsigned no_labels = 0;
label_entry *label_table;
unsigned label_ptr, sort_ptr;
unsigned lk_offset;
unsigned extra_loc_needed;

extern unsigned bchar;

void
init_planes(void)
{
    plane_max = 0;
    planes[plane_max] = NULL;
    char_max[plane_max] = 0;
}

void
init_character(unsigned c, char_entry *ready_made)
{
    register unsigned i, index, plane;
fprintf(stdout, "c=%x\n", c);

    if ((c<CHAR_MINIMUM) || (c>CHAR_MAXIMUM)) {
        warning_1("CHARACTER index (H %X) not 31-bit unsigned integer; "
                  "ignored", c);
        current_character = NULL;
        return;
    }
    plane = c / PLANE;
    index = c % PLANE;
    for (i=plane_max+1; i<=plane; i++) {
	    planes[plane] = NULL;
    }
    if (planes[plane]==NULL) {
        planes[plane] = (char_entry **)xmalloc(PLANE * sizeof(char_entry *));
        char_max[plane] = 0;
        planes[plane][0] = NULL;
    }
    for (i=char_max[plane]+1; i<=index; i++) {
        planes[plane][i] = NULL;
    }
    if (plane>plane_max)       plane_max = plane;
    if (index>char_max[plane]) char_max[plane] = index;

    if (planes[plane][index] != NULL) {
        if (planes[plane][index]->defined == FALSE) {
            current_character = planes[plane][index];
            current_character->defined = TRUE;
            return;
        }
        warning_1("CHARACTER index (H %X) previously defined; "
                  "old definition ignored", c);
        free(current_character);
        current_character = NULL;
    }
    if (ready_made != NULL) {
        current_character = ready_made;
    } else {
        current_character = (char_entry *)xmalloc(sizeof(char_entry));
        planes[plane][index] = current_character;
        for (i=C_MIN; i<=C_MAX; i++) {
            current_character->indices[i] = NULL;
        }
        current_character->c = c;
        current_character->remainder = 0;
        current_character->tag = TAG_NONE;
        current_character->defined = TRUE;
        current_character->accent = ACC_NONE;
        current_character->ovf_packet_length = 0;
        current_character->ovf_packet = NULL;
        cur_packet = NULL;
    }
}

/* This routine can only be called after init_character has been called */

void
copy_characters(unsigned c)
{
     unsigned i=0;
     char_entry *the_entry = current_character;

     if (the_entry == NULL)
         internal_error_0("copy_characters");
     for (i=the_entry->c; i<=c; i++)
         init_character(c, the_entry);
}

void
ensure_existence(unsigned c)
{
    register unsigned index, plane;
    plane = c / PLANE;
    index = c % PLANE;

    if ((planes[plane]==NULL) || (planes[plane][index]==NULL)) {
        init_character(c, NULL);
        planes[plane][index]->defined = FALSE;
    }
}

#define for_all_characters(FOR_ALL_CHARACTERS_ACTION) \
    for (plane = 0; plane <=plane_max; plane++) { \
        if (planes[plane] != NULL) { \
            for (index = 0; index <=char_max[plane]; index++) { \
                entry = planes[plane][index]; \
                c = plane*PLANE + index; \
                if (entry != NULL) {  \
                    FOR_ALL_CHARACTERS_ACTION \
                } \
            } \
        } \
    }

#define for_all_existing_characters(FOR_ALL_EXISTING_CHARACTERS_ACTION) \
    for_all_characters( \
        if (entry->defined == TRUE) { \
            FOR_ALL_EXISTING_CHARACTERS_ACTION \
        } \
    )

void
output_ovf_chars(void)
{
    register unsigned index, plane, c, k;
    char_entry *entry;
    fix wd;
    unsigned len;

    for_all_existing_characters(
        wd = lval(entry->indices[C_WD]);
        if (design_units != UNITY)
            wd = zround(((double)wd) / ((double)design_units) * 1048576.0);
        if ((entry->ovf_packet_length>241) ||
            (wd < 0) || (wd >= 0x1000000) ||
            (c < 0) || (c >255)) {
            out_ovf(242); out_ovf_4(entry->ovf_packet_length);
            out_ovf_4(c); out_ovf_4(wd);
        } else {
            out_ovf(entry->ovf_packet_length); out_ovf(c);
            out_ovf((wd >>16) & 0xff); out_ovf((wd >>8) & 0xff);
            out_ovf(wd & 0xff);
        }
        for (k=0; k<entry->ovf_packet_length; k++)
            out_ovf(entry->ovf_packet[k]);
    )
}


void
check_existence_all_character_fields(void)
{
    register unsigned index, plane, c;
    char_entry *entry;
    unsigned *exten;
    unsigned j;
 
    for_all_existing_characters(
        switch(entry->tag) {
            case TAG_NONE: { break; }
            case TAG_LIG: {
                check_ligature_program(c, entry->remainder);
                break;
            }
            case TAG_LIST:{
                check_existence_and_safety(c, entry->remainder, NULL,
                    "%sCharacter (H %X) NEXTLARGER than (H %X) "
                    "has no CHARACTER spec");
                break;
            }
            case TAG_EXT:{
                exten = entry->extens;
                for (j=E_MIN; j<=E_MAX; j++) {
                    if (exten[j]!=0)
                        check_existence_and_safety(c, exten[j],
                            extensible_pieces[j],
                            "%s piece (H %X) of character (H %X) "
                            "has no CHARACTER spec");
                }
                break;
            }
        }
    )
}  

void
clear_ligature_entries(void)
{
    register unsigned index, plane, c;
    char_entry *entry;

    for_all_existing_characters(
        if (entry->tag == TAG_LIG) {
            entry->tag = TAG_NONE;
            entry->remainder = 0;
        }
    )
}

void
check_existence_and_safety(unsigned c, unsigned g, string extra, string fmt)
{
    char_entry *gentry = planes[g/PLANE][g%PLANE];

    if ((g<CHAR_MINIMUM) || (g>CHAR_MAXIMUM))
        internal_error_1("check_existence_and_safety (g=%d)", g);
    gentry = planes[g/PLANE][g%PLANE];
    if ((g>=128) && (c<128))
        seven_bit_calculated = 0;
    if ((gentry==NULL) || (gentry->defined == FALSE)) {
        warning_s_2(fmt, extra, g, c);
        current_character = gentry;
        set_character_measure(C_WD, 0);
    }
}

void
doublecheck_existence(unsigned g, string extra, char*fmt)
{
    char_entry *gentry = planes[g/PLANE][g%PLANE];

    if ((g<CHAR_MINIMUM) || (g>CHAR_MAXIMUM))
        internal_error_1("doublecheck_existence (g=%d)", g);
    gentry = planes[g/PLANE][g%PLANE];
    if ((gentry==NULL) || (gentry->defined == FALSE)) {
        warning_s_1(fmt, extra, g);
        current_character = gentry;
/*
        set_character_measure(C_WD, 0);
*/
    }
}

extern string character_measures[];
void
print_characters(void)
{
    register unsigned index, plane, c;
    char_entry *entry;
    unsigned *exten;
    unsigned j,k;

    for_all_characters(
        print_character(c);
        for (k=C_MIN; k<C_MAX; k++) {
            if (entry->indices[k] != NULL) {
                print_character_measure(k, lval(entry->indices[k]));
            }
        }
        switch (entry->tag) {
            case TAG_NONE: { break; }
            case TAG_LIG:  { break; }
            case TAG_LIST: {
                print_next_larger(entry->remainder);
                break;
            }
            case TAG_EXT: {
	        print_var_character();
                exten = entry->extens;
                for (j=E_MIN; j<=E_MAX; j++) {
                    if (exten[j]!=0)
                        print_extensible_piece(j,exten[j]);
                }
                right();
                break;
            }
        }
        right();
    )
}


void
check_char_tag(unsigned c)
{
    ensure_existence(c);
}

void
set_char_tag(unsigned c, unsigned tag)
{
    ensure_existence(c);
    planes[c/PLANE][c%PLANE]->tag = tag;
}

void
set_char_remainder(unsigned c, unsigned remainder)
{
    ensure_existence(c);
    planes[c/PLANE][c%PLANE]->remainder = remainder;
}

int
get_char_remainder(unsigned c)
{
    ensure_existence(c);
    return planes[c/PLANE][c%PLANE]->remainder;
}

void
set_next_larger(unsigned larger)
{
    check_char_tag(current_character->c);
    set_char_tag(current_character->c, TAG_LIST);
    set_char_remainder(current_character->c, larger);
}

void
init_var_character(void)
{
    four_pieces *entry = (four_pieces *) xmalloc(sizeof(four_pieces));
    unsigned j;

    check_char_tag(current_character->c);
    set_char_tag(current_character->c, TAG_EXT);
    append_to_queue(&exten_queue, entry);
    for (j=E_MIN; j<=E_MAX; j++) {
        entry->pieces[j] = 0;
    }
    set_char_remainder(current_character->c, ne);
    current_character->extens = entry->pieces;
    ne++;
}

void
set_extensible_piece(unsigned piece, unsigned val)
{
    unsigned *exten = current_character->extens;

    if ((piece < E_MIN) || (piece > E_MAX))
        internal_error_1("set_extensible_piece (piece=%d)", piece);
    if (exten[piece]!=0)
        warning_0("value already defined");
    exten[piece] = val;
}

void
adjust_labels(void)
{
    unsigned plane, index;
    unsigned c;
    char_entry *entry;

    label_table = (label_entry *)xmalloc((no_labels+1)*sizeof(label_entry));
    label_ptr = 0;
    label_table[0].rr = -1; /* sentinel */
    for_all_characters(
        if (entry->tag == TAG_LIG) {
            sort_ptr = label_ptr; /* hole at position sort_ptr+1 */
            while (label_table[sort_ptr].rr > entry->remainder) {
                label_table[sort_ptr+1] = label_table[sort_ptr];
                sort_ptr--; /* move the hole */
            }
            label_table[sort_ptr+1].cc = c;
            label_table[sort_ptr+1].rr = entry->remainder;
            label_ptr++;
        }
    )
    if (bchar != -1) {
        extra_loc_needed = TRUE; lk_offset = 1;
    } else {
        extra_loc_needed = FALSE; lk_offset = 0;
    }
    sort_ptr = label_ptr; /* the largest unallocated label */
    if ((label_table[sort_ptr].rr + lk_offset) > MAX_START) {
        lk_offset=0; extra_loc_needed=FALSE;
        /* location 0 can do double duty */
        do {
            set_char_remainder(label_table[sort_ptr].cc, lk_offset);
            while (label_table[sort_ptr-1].rr == label_table[sort_ptr].rr) {
                sort_ptr--;
                set_char_remainder(label_table[sort_ptr].cc, lk_offset);
            }
            lk_offset++; sort_ptr--;
        } while ((lk_offset+label_table[sort_ptr].rr) >= MAX_START);
        /* N.B. lk_offset=MAX_START+1 satisfies this when sort_ptr=0 */
    }
    if (lk_offset>0) {
        while (sort_ptr>0) {
            set_char_remainder(label_table[sort_ptr].cc,
                               get_char_remainder(label_table[sort_ptr].cc)
                               +lk_offset);
            sort_ptr--;
        }
    }
}

void
print_labels(void)
{
    unsigned i;

    if (label_ptr>0) {
        left(); out("COMMENT"); out_ln();
        for (i=1; i<=label_ptr; i++) {
            left(); out("LABEL_ENTRY"); out(" ");
            out_int(i,10); out(" ");
            out_int(label_table[i].cc, 16); out(" ");
            out_int(label_table[i].rr, 10); right();
        }
        right();
    }
}

void
check_and_correct(void)
{
    build_kern_table();
    build_dimen_tables();
    build_exten_table();
    check_ligature_ends_properly();
    adjust_labels();
    check_existence_all_character_fields();
    calculate_seven_bit_safe_flag();
    check_ligature_infinite_loops();
    check_charlist_infinite_loops();
    doublecheck_ligatures();
    doublecheck_extens();
}

void
check_charlist_infinite_loops(void)
{
    unsigned plane, index;
    unsigned c;
    char_entry *entry;
    unsigned g;

    for_all_characters(
        if (entry->tag == TAG_LIST) {
            g = entry->remainder;
            while ((g < c) && (planes[g/PLANE][g%PLANE]->tag == TAG_LIST)) {
                g = planes[g/PLANE][g%PLANE]->remainder;
            }
            if (g == c) {
                entry->tag = TAG_NONE;
                entry->remainder = 0;
                warning_1("Cycle of NEXTLARGER characters "
                          "has been broken at ",c);
            }
        }
    )
}

void
build_exten_table(void)
{
    list L1 = exten_queue.front, L2;
    unsigned i = 0;

    exten_table = (four_pieces **) xmalloc(ne*sizeof(four_pieces *));
    while (L1 != NULL) {
        exten_table[i] = (four_pieces *)L1->contents;
        L2 = L1->ptr;
        free(L1); L1 = L2;
        i++;
    }
}

void
print_extens(void)
{
    unsigned i,j;

    if (ne>0) {
        left(); out("COMMENT"); out_ln();
        for (i=0; i<ne; i++) {
            left(); out("EXTEN_ENTRY");
            out(" "); out_int(i,10); out_ln();
            for (j=E_MIN; j<=E_MAX; j++) {
                if (exten_table[i]->pieces[j] != 0)
                    print_extensible_piece(j,exten_table[i]->pieces[j]);
            }
            right();
        }
        right();
    }
}

void
doublecheck_extens(void)
{
    unsigned i,j;

    if (ne>0) {
        for (i=0; i<ne; i++) {
            for (j=E_MIN; j<=E_MAX; j++) {
                if (exten_table[i]->pieces[j] != 0)
                    doublecheck_existence(
                        exten_table[i]->pieces[j], extensible_pieces[j],
                        "Unused %s piece (H %X) refers to "
                        "nonexistent character");
            }
        }
    }
}

void
compute_ligkern_offset(void)
{
}

void
compute_character_info_size(void)
{
}

void
output_ofm_extensible(void)
{
    unsigned i,j;

    for (i=0; i<ne; i++) {
        for (j=E_MIN; j<=E_MAX; j++) {
            if (exten_table[i]->pieces[j] != 0)
                out_ofm_char(exten_table[i]->pieces[j]);
            else out_ofm_char(0);
        }
    }
}

void
compute_ofm_character_info(void)
{
    unsigned plane, index;
    unsigned c;
    char_entry *entry;

    bc = 0x7fffffff; ec=0;
    switch (ofm_level) {
        case OFM_TFM: {
            for_all_existing_characters(
                if (c < bc) bc = c;
                if (c > ec) ec = c;
            )
            break;
        }
        case OFM_LEVEL0: {
            for_all_existing_characters(
                if (c < bc) bc = c;
                if (c > ec) ec = c;
            )
            break;
        }
        case OFM_LEVEL1: {
            internal_error_0("OFM level 1 not currently supported");
            break;
        }
        default: { internal_error_0("compute_ofm_character_info"); }
    }
}

void
output_ofm_character_info(void)
{
    unsigned plane, index;
    unsigned c;
    char_entry *entry;
    unsigned wd, ht, dp, ic;

    switch (ofm_level) {
        case OFM_TFM: {
            plane=0;
            for (index = bc; index <=ec; index++) {
                entry = planes[plane][index]; 
                c = plane*PLANE + index;
                if (entry == NULL) { 
                    out_ofm_4(0);
                } else {
                    if (entry->indices[C_WD] != NULL)
                        wd = entry->indices[C_WD]->index;
                    else wd = 0;
                    if (entry->indices[C_HT] != NULL)
                        ht = entry->indices[C_HT]->index;
                    else ht = 0;
                    if (entry->indices[C_DP] != NULL)
                        dp = entry->indices[C_DP]->index;
                    else dp = 0;
                    if (entry->indices[C_IC] != NULL)
                        ic = entry->indices[C_IC]->index;
                    else ic = 0;
                    out_ofm(wd);
                    out_ofm(ht*16 + dp);
                    out_ofm(ic*4 + entry->tag);
                    out_ofm(entry->remainder);
                }
            }
            break;
        }
        case OFM_LEVEL0: {
            plane=0;
            for (index = bc; index <=ec; index++) {
                entry = planes[plane][index]; 
                c = plane*PLANE + index;
                if (entry == NULL) { 
                    out_ofm_4(0); out_ofm_4(0);
                } else {
                    if (entry->indices[C_WD] != NULL)
                        wd = entry->indices[C_WD]->index;
                    else wd = 0;
                    if (entry->indices[C_HT] != NULL)
                        ht = entry->indices[C_HT]->index;
                    else ht = 0;
                    if (entry->indices[C_DP] != NULL)
                        dp = entry->indices[C_DP]->index;
                    else dp = 0;
                    if (entry->indices[C_IC] != NULL)
                        ic = entry->indices[C_IC]->index;
                    else ic = 0;
                    out_ofm_2(wd);
                    out_ofm(ht);
                    out_ofm(dp);
                    out_ofm(ic);
                    out_ofm(entry->tag);
                    out_ofm_2(entry->remainder);
                }
            }
            break;
        }
        case OFM_LEVEL1: {
            internal_error_0("OFM level 1 not currently supported");
            break;
        }
        default: { internal_error_0("compute_ofm_character_info"); }
    }
}
