C++程序  |  1078行  |  34.06 KB

/* Libhnj is dual licensed under LGPL and MPL. Boilerplate for both
 * licenses follows.
 */

/* LibHnj - a library for high quality hyphenation and justification
 * Copyright (C) 1998 Raph Levien, 
 * 	     (C) 2001 ALTLinux, Moscow (http://www.alt-linux.org), 
 *           (C) 2001 Peter Novodvorsky (nidd@cs.msu.su)
 *           (C) 2006, 2007, 2008 László Németh (nemeth at OOo)
 *
 * 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.
 */

/*
 * The contents of this file are subject to the Mozilla Public License
 * Version 1.0 (the "MPL"); you may not use this file except in
 * compliance with the MPL.  You may obtain a copy of the MPL at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the MPL is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the MPL
 * for the specific language governing rights and limitations under the
 * MPL.
 *
 */
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <stdlib.h> /* for NULL, malloc */
#include <stdio.h>  /* for fprintf */
#include <string.h> /* for strdup */
#include <unistd.h> /* for close */

#define noVERBOSE

#include "hnjalloc.h"
#include "hyphen.h"

static char *
hnj_strdup (const char *s)
{
    char *new;
    int l;

    l = strlen (s);
    new = hnj_malloc (l + 1);
    memcpy (new, s, l);
    new[l] = 0;
    return new;
}

/* remove cross-platform text line end characters */
void hnj_strchomp(char * s)
{
    int k = strlen(s);
    if ((k > 0) && ((*(s+k-1)=='\r') || (*(s+k-1)=='\n'))) *(s+k-1) = '\0';
    if ((k > 1) && (*(s+k-2) == '\r')) *(s+k-2) = '\0';
}

/* a little bit of a hash table implementation. This simply maps strings
   to state numbers */

typedef struct _HashTab HashTab;
typedef struct _HashEntry HashEntry;

/* A cheap, but effective, hack. */
#define HASH_SIZE 31627

struct _HashTab {
    HashEntry *entries[HASH_SIZE];
};

struct _HashEntry {
    HashEntry *next;
    char *key;
    int val;
};

/* a char* hash function from ASU - adapted from Gtk+ */
static unsigned int
hnj_string_hash (const char *s)
{
    const char *p;
    unsigned int h=0, g;
    for(p = s; *p != '\0'; p += 1) {
        h = ( h << 4 ) + *p;
        if ( ( g = h & 0xf0000000 ) ) {
            h = h ^ (g >> 24);
            h = h ^ g;
        }
    }
    return h /* % M */;
}

static HashTab *
hnj_hash_new (void)
{
    HashTab *hashtab;
    int i;

    hashtab = hnj_malloc (sizeof(HashTab));
    for (i = 0; i < HASH_SIZE; i++)
        hashtab->entries[i] = NULL;

    return hashtab;
}

static void
hnj_hash_free (HashTab *hashtab)
{
    int i;
    HashEntry *e, *next;

    for (i = 0; i < HASH_SIZE; i++)
        for (e = hashtab->entries[i]; e; e = next)
        {
            next = e->next;
            hnj_free (e->key);
            hnj_free (e);
        }

    hnj_free (hashtab);
}

/* assumes that key is not already present! */
static void
hnj_hash_insert (HashTab *hashtab, const char *key, int val)
{
    int i;
    HashEntry *e;

    i = hnj_string_hash (key) % HASH_SIZE;
    e = hnj_malloc (sizeof(HashEntry));
    e->next = hashtab->entries[i];
    e->key = hnj_strdup (key);
    e->val = val;
    hashtab->entries[i] = e;
}

/* return val if found, otherwise -1 */
static int
hnj_hash_lookup (HashTab *hashtab, const char *key)
{
    int i;
    HashEntry *e;
    i = hnj_string_hash (key) % HASH_SIZE;
    for (e = hashtab->entries[i]; e; e = e->next)
        if (!strcmp (key, e->key))
            return e->val;
    return -1;
}

/* Get the state number, allocating a new state if necessary. */
static int
hnj_get_state (HyphenDict *dict, HashTab *hashtab, const char *string)
{
    int state_num;

    state_num = hnj_hash_lookup (hashtab, string);

    if (state_num >= 0)
        return state_num;

    hnj_hash_insert (hashtab, string, dict->num_states);
    /* predicate is true if dict->num_states is a power of two */
    if (!(dict->num_states & (dict->num_states - 1)))
    {
        dict->states = hnj_realloc (dict->states,
            (dict->num_states << 1) *
            sizeof(HyphenState));
    }
    dict->states[dict->num_states].match = NULL;
    dict->states[dict->num_states].repl = NULL;
    dict->states[dict->num_states].fallback_state = -1;
    dict->states[dict->num_states].num_trans = 0;
    dict->states[dict->num_states].trans = NULL;
    return dict->num_states++;
}

/* add a transition from state1 to state2 through ch - assumes that the
   transition does not already exist */
static void
hnj_add_trans (HyphenDict *dict, int state1, int state2, char ch)
{
    int num_trans;

    num_trans = dict->states[state1].num_trans;
    if (num_trans == 0)
    {
        dict->states[state1].trans = hnj_malloc (sizeof(HyphenTrans));
    }
    else if (!(num_trans & (num_trans - 1)))
    {
        dict->states[state1].trans = hnj_realloc (dict->states[state1].trans,
            (num_trans << 1) *
            sizeof(HyphenTrans));
    }
    dict->states[state1].trans[num_trans].ch = ch;
    dict->states[state1].trans[num_trans].new_state = state2;
    dict->states[state1].num_trans++;
}

#ifdef VERBOSE
HashTab *global;

static char *
get_state_str (int state)
{
    int i;
    HashEntry *e;

    for (i = 0; i < HASH_SIZE; i++)
        for (e = global->entries[i]; e; e = e->next)
            if (e->val == state)
                return e->key;
    return NULL;
}
#endif

// Get a line from the dictionary contents.
static char *
get_line (char *s, int size, const char *dict_contents, int dict_length,
    int *dict_ptr)
{
    int len = 0;
    while (len < (size - 1) && *dict_ptr < dict_length) {
        s[len++] = *(dict_contents + *dict_ptr);
        (*dict_ptr)++;
        if (s[len - 1] == '\n')
            break;
    }
    s[len] = '\0';
    if (len > 0) {
        return s;
    } else {
        return NULL;
    }
}

HyphenDict *
hnj_hyphen_load (const char *fn)
{
    if (fn == NULL)
        return NULL;
    const int fd = open(fn, O_RDONLY);
    if (fd == -1)
        return NULL;
    struct stat sb;
    if (fstat(fd, &sb) == -1)  {  /* To obtain file size */
        close(fd);
        return NULL;
    }

    const char *addr = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
    if (addr == MAP_FAILED) {
        close(fd);
        return NULL;
    }
    HyphenDict *dict = hnj_hyphen_load_from_buffer(addr, sb.st_size);
    munmap((void *)addr, sb.st_size);
    close(fd);

    return dict;
}

HyphenDict *
hnj_hyphen_load_from_buffer (const char *dict_contents, int dict_length)
{
    HyphenDict *dict[2];
    HashTab *hashtab;
    char buf[MAX_CHARS];
    char word[MAX_CHARS];
    char pattern[MAX_CHARS];
    char * repl;
    signed char replindex;
    signed char replcut;
    int state_num = 0, last_state;
    int i, j, k;
    char ch;
    int found;
    HashEntry *e;
    int nextlevel = 0;

    if (dict_contents == NULL)
        return NULL;

    int dict_ptr = 0;
// loading one or two dictionaries (separated by NEXTLEVEL keyword)
    for (k = 0; k == 0 || (k == 1 && nextlevel); k++) { 
        hashtab = hnj_hash_new ();
#ifdef VERBOSE
        global = hashtab;
#endif
        hnj_hash_insert (hashtab, "", 0);
        dict[k] = hnj_malloc (sizeof(HyphenDict));
        dict[k]->num_states = 1;
        dict[k]->states = hnj_malloc (sizeof(HyphenState));
        dict[k]->states[0].match = NULL;
        dict[k]->states[0].repl = NULL;
        dict[k]->states[0].fallback_state = -1;
        dict[k]->states[0].num_trans = 0;
        dict[k]->states[0].trans = NULL;
        dict[k]->nextlevel = NULL;
        dict[k]->lhmin = 0;
        dict[k]->rhmin = 0;
        dict[k]->clhmin = 0;
        dict[k]->crhmin = 0;

        /* read in character set info */
        if (k == 0) {
            for (i=0;i<MAX_NAME;i++) dict[k]->cset[i]= 0;
            get_line(dict[k]->cset, sizeof(dict[k]->cset), dict_contents,
                dict_length, &dict_ptr);
            for (i=0;i<MAX_NAME;i++)
                if ((dict[k]->cset[i] == '\r') || (dict[k]->cset[i] == '\n'))
                    dict[k]->cset[i] = 0;
            dict[k]->utf8 = (strcmp(dict[k]->cset, "UTF-8") == 0);
        } else {
            strcpy(dict[k]->cset, dict[0]->cset);
            dict[k]->utf8 = dict[0]->utf8;
        }

        while (get_line(buf, sizeof(buf), dict_contents, dict_length,
                &dict_ptr) != NULL)
        {
            if (buf[0] != '%')
            {
                if (strncmp(buf, "NEXTLEVEL", 9) == 0) {
                    nextlevel = 1;
                    break;
                } else if (strncmp(buf, "LEFTHYPHENMIN", 13) == 0) {
                    dict[k]->lhmin = atoi(buf + 13);
                    continue;
                } else if (strncmp(buf, "RIGHTHYPHENMIN", 14) == 0) {
                    dict[k]->rhmin = atoi(buf + 14);
                    continue;
                } else if (strncmp(buf, "COMPOUNDLEFTHYPHENMIN", 21) == 0) {
                    dict[k]->clhmin = atoi(buf + 21);
                    continue;
                } else if (strncmp(buf, "COMPOUNDRIGHTHYPHENMIN", 22) == 0) {
                    dict[k]->crhmin = atoi(buf + 22);
                    continue;
                } 
                j = 0;
                pattern[j] = '0';
                repl = strchr(buf, '/');
                replindex = 0;
                replcut = 0;
                if (repl) {
                    char * index = strchr(repl + 1, ',');
                    *repl = '\0';
                    if (index) {
                        char * index2 = strchr(index + 1, ',');
                        *index = '\0';
                        if (index2) {
                            *index2 = '\0';
                            replindex = (signed char) atoi(index + 1) - 1;
                            replcut = (signed char) atoi(index2 + 1);                
                        }
                    } else {
                        hnj_strchomp(repl + 1);
                        replindex = 0;
                        replcut = strlen(buf);
                    }
                    repl = hnj_strdup(repl + 1);
                }
                for (i = 0; ((buf[i] > ' ') || (buf[i] < 0)); i++)
                {
                    if (buf[i] >= '0' && buf[i] <= '9')
                        pattern[j] = buf[i];
                    else
                    {
                        word[j] = buf[i];
                        pattern[++j] = '0';
                    }
                }
                word[j] = '\0';
                pattern[j + 1] = '\0';

                i = 0;
                if (!repl) {
                    /* Optimize away leading zeroes */
                    for (; pattern[i] == '0'; i++);
                } else {
                    if (*word == '.') i++;
                    /* convert UTF-8 char. positions of discretionary hyph. replacements to 8-bit */
                    if (dict[k]->utf8) {
                        int pu = -1;        /* unicode character position */
                        int ps = -1;        /* unicode start position (original replindex) */
                        int pc = (*word == '.') ? 1: 0; /* 8-bit character position */
                        for (; pc < (strlen(word) + 1); pc++) {
                            /* beginning of an UTF-8 character (not '10' start bits) */
                            if ((((unsigned char) word[pc]) >> 6) != 2) pu++;
                            if ((ps < 0) && (replindex == pu)) {
                                ps = replindex;
                                replindex = pc;
                            }
                            if ((ps >= 0) && ((pu - ps) == replcut)) {
                                replcut = (pc - replindex);
                                break;
                            }
                        }
                        if (*word == '.') replindex--;
                    }
                }

#ifdef VERBOSE
                printf ("word %s pattern %s, j = %d  repl: %s\n", word, pattern + i, j, repl);
#endif
                found = hnj_hash_lookup (hashtab, word);
                state_num = hnj_get_state (dict[k], hashtab, word);
                dict[k]->states[state_num].match = hnj_strdup (pattern + i);
                dict[k]->states[state_num].repl = repl;
                dict[k]->states[state_num].replindex = replindex;
                if (!replcut) {
                    dict[k]->states[state_num].replcut = strlen(word);
                } else {
                    dict[k]->states[state_num].replcut = replcut;
                }

                /* now, put in the prefix transitions */
                for (; found < 0 ;j--)
                {
                    last_state = state_num;
                    ch = word[j - 1];
                    word[j - 1] = '\0';
                    found = hnj_hash_lookup (hashtab, word);
                    state_num = hnj_get_state (dict[k], hashtab, word);
                    hnj_add_trans (dict[k], state_num, last_state, ch);
                }
            }
        }

        /* Could do unioning of matches here (instead of the preprocessor script).
           If we did, the pseudocode would look something like this:

           foreach state in the hash table
           foreach i = [1..length(state) - 1]
           state to check is substr (state, i)
           look it up
           if found, and if there is a match, union the match in.

           It's also possible to avoid the quadratic blowup by doing the
           search in order of increasing state string sizes - then you
           can break the loop after finding the first match.

           This step should be optional in any case - if there is a
           preprocessed rule table, it's always faster to use that.

        */

        /* put in the fallback states */
        for (i = 0; i < HASH_SIZE; i++)
            for (e = hashtab->entries[i]; e; e = e->next)
            {
                if (*(e->key)) for (j = 1; 1; j++)
                               {          
                                   state_num = hnj_hash_lookup (hashtab, e->key + j);
                                   if (state_num >= 0)
                                       break;
                               }
                /* KBH: FIXME state 0 fallback_state should always be -1? */
                if (e->val)
                    dict[k]->states[e->val].fallback_state = state_num;
            }
#ifdef VERBOSE
        for (i = 0; i < HASH_SIZE; i++)
            for (e = hashtab->entries[i]; e; e = e->next)
            {
                printf ("%d string %s state %d, fallback=%d\n", i, e->key, e->val,
                    dict[k]->states[e->val].fallback_state);
                for (j = 0; j < dict[k]->states[e->val].num_trans; j++)
                    printf (" %c->%d\n", dict[k]->states[e->val].trans[j].ch,
                        dict[k]->states[e->val].trans[j].new_state);
            }
#endif

#ifndef VERBOSE
        hnj_hash_free (hashtab);
#endif
        state_num = 0;
    }
    if (k == 2) dict[0]->nextlevel = dict[1];
    return dict[0];
}

void hnj_hyphen_free (HyphenDict *dict)
{
    int state_num;
    HyphenState *hstate;

    for (state_num = 0; state_num < dict->num_states; state_num++)
    {
        hstate = &dict->states[state_num];
        if (hstate->match)
            hnj_free (hstate->match);
        if (hstate->repl)
            hnj_free (hstate->repl);
        if (hstate->trans)
            hnj_free (hstate->trans);
    }
    if (dict->nextlevel) hnj_hyphen_free(dict->nextlevel);

    hnj_free (dict->states);

    hnj_free (dict);
}

#define MAX_WORD 256

int hnj_hyphen_hyphenate (HyphenDict *dict,
    const char *word, int word_size,
    char *hyphens)
{
    char prep_word_buf[MAX_WORD];
    char *prep_word;
    int i, j, k;
    int state;
    char ch;
    HyphenState *hstate;
    char *match;
    int offset;

    if (word_size + 3 < MAX_WORD)
        prep_word = prep_word_buf;
    else
        prep_word = hnj_malloc (word_size + 3);

    j = 0;
    prep_word[j++] = '.';
  
    for (i = 0; i < word_size; i++)
        prep_word[j++] = word[i];

    prep_word[j++] = '.';
    prep_word[j] = '\0';
      
    for (i = 0; i < j; i++)                                                       
        hyphens[i] = '0';    
  
#ifdef VERBOSE
    printf ("prep_word = %s\n", prep_word);
#endif

    /* now, run the finite state machine */
    state = 0;
    for (i = 0; i < j; i++)
    {
        ch = prep_word[i];
        for (;;)
        {

            if (state == -1) {
                /* return 1; */
                /*  KBH: FIXME shouldn't this be as follows? */
                state = 0;
                goto try_next_letter;
            }          

#ifdef VERBOSE
            char *state_str;
            state_str = get_state_str (state);

            for (k = 0; k < i - strlen (state_str); k++)
                putchar (' ');
            printf ("%s", state_str);
#endif

            hstate = &dict->states[state];
            for (k = 0; k < hstate->num_trans; k++)
                if (hstate->trans[k].ch == ch)
                {
                    state = hstate->trans[k].new_state;
                    goto found_state;
                }
            state = hstate->fallback_state;
#ifdef VERBOSE
            printf (" falling back, fallback_state %d\n", state);
#endif
        }
      found_state:
#ifdef VERBOSE
        printf ("found state %d\n",state);
#endif
        /* Additional optimization is possible here - especially,
           elimination of trailing zeroes from the match. Leading zeroes
           have already been optimized. */
        match = dict->states[state].match;
        /* replacing rules not handled by hyphen_hyphenate() */
        if (match && !dict->states[state].repl)
        {
            offset = i + 1 - strlen (match);
#ifdef VERBOSE
            for (k = 0; k < offset; k++)
                putchar (' ');
            printf ("%s\n", match);
#endif
            /* This is a linear search because I tried a binary search and
               found it to be just a teeny bit slower. */
            for (k = 0; match[k]; k++)
                if (hyphens[offset + k] < match[k])
                    hyphens[offset + k] = match[k];
        }

        /* KBH: we need this to make sure we keep looking in a word */
        /* for patterns even if the current character is not known in state 0 */
        /* since patterns for hyphenation may occur anywhere in the word */
      try_next_letter: ;

    }
#ifdef VERBOSE
    for (i = 0; i < j; i++)
        putchar (hyphens[i]);
    putchar ('\n');
#endif

    for (i = 0; i < j - 4; i++)
#if 0
        if (hyphens[i + 1] & 1)
            hyphens[i] = '-';
#else
    hyphens[i] = hyphens[i + 1];
#endif
    hyphens[0] = '0';
    for (; i < word_size; i++)
        hyphens[i] = '0';
    hyphens[word_size] = '\0';

    if (prep_word != prep_word_buf)
        hnj_free (prep_word);
    
    return 0;    
}

/* character length of the first n byte of the input word */
int hnj_hyphen_strnlen(const char * word, int n, int utf8)
{
    int i = 0;
    int j = 0;
    while (j < n && word[j] != '\0') {
        i++;
        for (j++; utf8 && (word[j] & 0xc0) == 0x80; j++);
    }
    return i;
}

int hnj_hyphen_lhmin(int utf8, const char *word, int word_size, char * hyphens,
	char *** rep, int ** pos, int ** cut, int lhmin)
{
    int i, j;
    for (i = 1, j = 0; i < lhmin && word[j] != '\0'; i++) do {
            // check length of the non-standard part
            if (*rep && *pos && *cut && (*rep)[j]) {
                char * rh = strchr((*rep)[j], '=');
                if (rh && (hnj_hyphen_strnlen(word, j - (*pos)[j] + 1, utf8) +
                        hnj_hyphen_strnlen((*rep)[j], rh - (*rep)[j], utf8)) < lhmin) {
                    free((*rep)[j]);
                    (*rep)[j] = NULL;
                    hyphens[j] = '0';
                }
            } else {
                hyphens[j] = '0';
            }
            j++;
        } while (utf8 && (word[j + 1] & 0xc0) == 0xc0);
    return 0;
}

int hnj_hyphen_rhmin(int utf8, const char *word, int word_size, char * hyphens,
	char *** rep, int ** pos, int ** cut, int rhmin)
{
    int i;
    int j = word_size - 2;    
    for (i = 1; i < rhmin && j > 0; j--) {
        // check length of the non-standard part
        if (*rep && *pos && *cut && (*rep)[j]) {
            char * rh = strchr((*rep)[j], '=');
            if (rh && (hnj_hyphen_strnlen(word + j - (*pos)[j] + (*cut)[j] + 1, 100, utf8) +
                    hnj_hyphen_strnlen(rh + 1, strlen(rh + 1), utf8)) < rhmin) {
                free((*rep)[j]);
                (*rep)[j] = NULL;
                hyphens[j] = '0';
            }
        } else {
            hyphens[j] = '0';
        }
        if (!utf8 || (word[j] & 0xc0) != 0xc0) i++;
    }
    return 0;
}

// recursive function for compound level hyphenation
int hnj_hyphen_hyph_(HyphenDict *dict, const char *word, int word_size,
    char * hyphens, char *** rep, int ** pos, int ** cut,
    int clhmin, int crhmin, int lend, int rend)
{
    char prep_word_buf[MAX_WORD];
    char *prep_word;
    int i, j, k;
    int state;
    char ch;
    HyphenState *hstate;
    char *match;
    char *repl;
    signed char replindex;
    signed char replcut;
    int offset;
    int matchlen_buf[MAX_CHARS];
    int matchindex_buf[MAX_CHARS];
    char * matchrepl_buf[MAX_CHARS];
    int * matchlen;
    int * matchindex;
    char ** matchrepl;  
    int isrepl = 0;
    int nHyphCount;

    if (word_size + 3 < MAX_CHARS) {
        prep_word = prep_word_buf;
        matchlen = matchlen_buf;
        matchindex = matchindex_buf;
        matchrepl = matchrepl_buf;
    } else {
        prep_word = hnj_malloc (word_size + 3);
        matchlen = hnj_malloc ((word_size + 3) * sizeof(int));
        matchindex = hnj_malloc ((word_size + 3) * sizeof(int));
        matchrepl = hnj_malloc ((word_size + 3) * sizeof(char *));
    }

    j = 0;
    prep_word[j++] = '.';
  
    for (i = 0; i < word_size; i++)
        prep_word[j++] = word[i];

    prep_word[j++] = '.';
    prep_word[j] = '\0';

    for (i = 0; i < j; i++)
        hyphens[i] = '0';    

#ifdef VERBOSE
    printf ("prep_word = %s\n", prep_word);
#endif

    /* now, run the finite state machine */
    state = 0;
    for (i = 0; i < j; i++)
    {
        ch = prep_word[i];
        for (;;)
        {

            if (state == -1) {
                /* return 1; */
                /*  KBH: FIXME shouldn't this be as follows? */
                state = 0;
                goto try_next_letter;
            }          

#ifdef VERBOSE
            char *state_str;
            state_str = get_state_str (state);

            for (k = 0; k < i - strlen (state_str); k++)
                putchar (' ');
            printf ("%s", state_str);
#endif

            hstate = &dict->states[state];
            for (k = 0; k < hstate->num_trans; k++)
                if (hstate->trans[k].ch == ch)
                {
                    state = hstate->trans[k].new_state;
                    goto found_state;
                }
            state = hstate->fallback_state;
#ifdef VERBOSE
            printf (" falling back, fallback_state %d\n", state);
#endif
        }
      found_state:
#ifdef VERBOSE
        printf ("found state %d\n",state);
#endif
        /* Additional optimization is possible here - especially,
           elimination of trailing zeroes from the match. Leading zeroes
           have already been optimized. */
        match = dict->states[state].match;
        repl = dict->states[state].repl;
        replindex = dict->states[state].replindex;
        replcut = dict->states[state].replcut;
        /* replacing rules not handled by hyphen_hyphenate() */
        if (match)
        {
            offset = i + 1 - strlen (match);
#ifdef VERBOSE
            for (k = 0; k < offset; k++)
                putchar (' ');
            printf ("%s (%s)\n", match, repl);
#endif
            if (repl) {
                if (!isrepl) for(; isrepl < word_size; isrepl++) {
                        matchrepl[isrepl] = NULL;
                        matchindex[isrepl] = -1;
                    }
                matchlen[offset + replindex] = replcut;
            }
            /* This is a linear search because I tried a binary search and
               found it to be just a teeny bit slower. */
            for (k = 0; match[k]; k++) {
                if ((hyphens[offset + k] < match[k])) {
                    hyphens[offset + k] = match[k];
                    if (match[k]&1) {
                        matchrepl[offset + k] = repl;
                        if (repl && (k >= replindex) && (k <= replindex + replcut)) {
                            matchindex[offset + replindex] = offset + k;
                        }
                    }
                }
            }
          
        }

        /* KBH: we need this to make sure we keep looking in a word */
        /* for patterns even if the current character is not known in state 0 */
        /* since patterns for hyphenation may occur anywhere in the word */
      try_next_letter: ;

    }
#ifdef VERBOSE
    for (i = 0; i < j; i++)
        putchar (hyphens[i]);
    putchar ('\n');
#endif

    for (i = 0; i < j - 3; i++)
#if 0
        if (hyphens[i + 1] & 1)
            hyphens[i] = '-';
#else
    hyphens[i] = hyphens[i + 1];
#endif
    for (; i < word_size; i++)
        hyphens[i] = '0';
    hyphens[word_size] = '\0';

    /* now create a new char string showing hyphenation positions */
    /* count the hyphens and allocate space for the new hyphenated string */
    nHyphCount = 0;
    for (i = 0; i < word_size; i++)
        if (hyphens[i]&1)
            nHyphCount++;
    j = 0;
    for (i = 0; i < word_size; i++) {
        if (isrepl && (matchindex[i] >= 0) && matchrepl[matchindex[i]]) { 
            if (rep && pos && cut) {
                if (!*rep && !*pos && !*cut) {
                    int k;
                    *rep = (char **) malloc(sizeof(char *) * word_size);
                    *pos = (int *) malloc(sizeof(int) * word_size);
                    *cut = (int *) malloc(sizeof(int) * word_size);
                    for (k = 0; k < word_size; k++) {
                        (*rep)[k] = NULL;
                        (*pos)[k] = 0;
                        (*cut)[k] = 0;
                    }
                }
                (*rep)[matchindex[i] - 1] = hnj_strdup(matchrepl[matchindex[i]]);
                (*pos)[matchindex[i] - 1] = matchindex[i] - i;
                (*cut)[matchindex[i] - 1] = matchlen[i];
            }
            j += strlen(matchrepl[matchindex[i]]);
            i += matchlen[i] - 1;
        }
    }

    if (matchrepl != matchrepl_buf) {
        hnj_free (matchrepl);
        hnj_free (matchlen);
        hnj_free (matchindex);
    }

    // recursive hyphenation of the first (compound) level segments
    if (dict->nextlevel) {
        char * rep2_buf[MAX_WORD];
        int pos2_buf[MAX_WORD];
        int cut2_buf[MAX_WORD];
        char hyphens2_buf[MAX_WORD];
        char ** rep2;
        int * pos2;
        int * cut2;
        char * hyphens2;
        int begin = 0;
        if (word_size < MAX_CHARS) {
            rep2 = rep2_buf;
            pos2 = pos2_buf;
            cut2 = cut2_buf;
            hyphens2 = hyphens2_buf;
        } else {
            rep2 = hnj_malloc (word_size * sizeof(char *));
            pos2 = hnj_malloc (word_size * sizeof(int));
            cut2 = hnj_malloc (word_size * sizeof(int));
            hyphens2 = hnj_malloc (word_size);
        }
        for (i = 0; i < word_size; i++) rep2[i] = NULL;
        for (i = 0; i < word_size; i++)
            if (hyphens[i]&1 || (begin > 0 && i + 1 == word_size)) {
                if (i - begin > 1) {
                    int hyph = 0;
                    prep_word[i + 2] = '\0';
                    /* non-standard hyphenation at compound boundary (Schiffahrt) */
                    if (*rep && *pos && *cut && (*rep)[i]) {
                        char * l = strchr((*rep)[i], '=');
                        strcpy(prep_word + 2 + i - (*pos)[i], (*rep)[i]);
                        if (l) {
                            hyph = (l - (*rep)[i]) - (*pos)[i];
                            prep_word[2 + i + hyph] = '\0';
                        }
                    }
                    hnj_hyphen_hyph_(dict, prep_word + begin + 1, i - begin + 1 + hyph,
                        hyphens2, &rep2, &pos2, &cut2, clhmin,
                        crhmin, (begin > 0 ? 0 : lend), (hyphens[i]&1 ? 0 : rend));
                    for (j = 0; j < i - begin - 1; j++) {
                        hyphens[begin + j] = hyphens2[j];
                        if (rep2[j] && rep && pos && cut) {
                            if (!*rep && !*pos && !*cut) {
                                int k;
                                *rep = (char **) malloc(sizeof(char *) * word_size);
                                *pos = (int *) malloc(sizeof(int) * word_size);
                                *cut = (int *) malloc(sizeof(int) * word_size);
                                for (k = 0; k < word_size; k++) {
                                    (*rep)[k] = NULL;
                                    (*pos)[k] = 0;
                                    (*cut)[k] = 0;
                                }
                            }
                            (*rep)[begin + j] = rep2[j];
                            (*pos)[begin + j] = pos2[j];
                            (*cut)[begin + j] = cut2[j];
                        }
                    }
                    prep_word[i + 2] = word[i + 1];
                    if (*rep && *pos && *cut && (*rep)[i]) {
                        strcpy(prep_word + 1, word);
                    }
                }
                begin = i + 1;
                for (j = 0; j < word_size; j++) rep2[j] = NULL;
            }
     
        // non-compound
        if (begin == 0) {
            hnj_hyphen_hyph_(dict->nextlevel, word, word_size,
                hyphens, rep, pos, cut, clhmin, crhmin, lend, rend);
            if (!lend) hnj_hyphen_lhmin(dict->utf8, word, word_size, hyphens,
                rep, pos, cut, clhmin);
            if (!rend) hnj_hyphen_rhmin(dict->utf8, word, word_size, hyphens,
                rep, pos, cut, crhmin);
        }
     
        if (rep2 != rep2_buf) {
            free(rep2);
            free(cut2);
            free(pos2);
            free(hyphens2);
        }
    }

    if (prep_word != prep_word_buf) hnj_free (prep_word);
    return 0;
}

/* UTF-8 normalization of hyphen and non-standard positions */
int hnj_hyphen_norm(const char *word, int word_size, char * hyphens,
	char *** rep, int ** pos, int ** cut)
{
    if ((((unsigned char) word[0]) >> 6) == 2) {
        fprintf(stderr, "error - bad, non UTF-8 input: %s\n", word);
        return 1;
    }

    /* calculate UTF-8 character positions */
    int i, j, k;
    for (i = 0, j = -1; i < word_size; i++) {
        /* beginning of an UTF-8 character (not '10' start bits) */
        if ((((unsigned char) word[i]) >> 6) != 2) j++;
        hyphens[j] = hyphens[i];
        if (rep && pos && cut && *rep && *pos && *cut) {
            int l = (*pos)[i];
            (*pos)[j] = 0;
            for (k = 0; k < l; k++) {
                if ((((unsigned char) word[i - k]) >> 6) != 2) (*pos)[j]++;
            }
            k = i - l + 1;
            l = k + (*cut)[i];
            (*cut)[j] = 0;        
            for (; k < l; k++) {
                if ((((unsigned char) word[k]) >> 6) != 2) (*cut)[j]++;
            }
            (*rep)[j] = (*rep)[i];
            if (j < i) {
                (*rep)[i] = NULL;
                (*pos)[i] = 0;
                (*cut)[i] = 0;
            }
        }
    }
    hyphens[j + 1] = '\0';
    return 0;
}

/* get the word with all possible hyphenations (output: hyphword) */
void hnj_hyphen_hyphword(const char * word, int l, const char * hyphens, 
    char * hyphword, char *** rep, int ** pos, int ** cut)
{
    int i, j;
    for (i = 0, j = 0; i < l; i++, j++) {
        if (hyphens[i]&1) {
            hyphword[j] = word[i];
            if (*rep && *pos && *cut && (*rep)[i]) {
                strcpy(hyphword + j - (*pos)[i] + 1, (*rep)[i]);
                j += strlen((*rep)[i]) - (*pos)[i];
                i += (*cut)[i] - (*pos)[i];
            } else hyphword[++j] = '=';
        } else hyphword[j] = word[i];
    }
    hyphword[j] = '\0';
}


/* main api function with default hyphenmin parameters */
int hnj_hyphen_hyphenate2 (HyphenDict *dict,
    const char *word, int word_size, char * hyphens,
    char *hyphword, char *** rep, int ** pos, int ** cut)
{
    hnj_hyphen_hyph_(dict, word, word_size, hyphens, rep, pos, cut,
        dict->clhmin, dict->crhmin, 1, 1);
    hnj_hyphen_lhmin(dict->utf8, word, word_size,
        hyphens, rep, pos, cut, (dict->lhmin > 0 ? dict->lhmin : 2));
    hnj_hyphen_rhmin(dict->utf8, word, word_size,
        hyphens, rep, pos, cut, (dict->rhmin > 0 ? dict->rhmin : 2));
    if (hyphword) hnj_hyphen_hyphword(word, word_size, hyphens, hyphword, rep, pos, cut);
    if (dict->utf8) return hnj_hyphen_norm(word, word_size, hyphens, rep, pos, cut);
    return 0;
}

/* previous main api function with hyphenmin parameters */
int hnj_hyphen_hyphenate3 (HyphenDict *dict,
	const char *word, int word_size, char * hyphens,
	char *hyphword, char *** rep, int ** pos, int ** cut,
	int lhmin, int rhmin, int clhmin, int crhmin)
{
    lhmin = (lhmin > 0 ? lhmin : dict->lhmin);
    rhmin = (rhmin > 0 ? rhmin : dict->rhmin);
    hnj_hyphen_hyph_(dict, word, word_size, hyphens, rep, pos, cut,
        clhmin, crhmin, 1, 1);
    hnj_hyphen_lhmin(dict->utf8, word, word_size, hyphens,
        rep, pos, cut, (lhmin > 0 ? lhmin : 2));
    hnj_hyphen_rhmin(dict->utf8, word, word_size, hyphens,
        rep, pos, cut, (rhmin > 0 ? rhmin : 2));
    if (hyphword) hnj_hyphen_hyphword(word, word_size, hyphens, hyphword, rep, pos, cut);
    if (dict->utf8) return hnj_hyphen_norm(word, word_size, hyphens, rep, pos, cut);
    return 0;
}