shithub: neatroff

Download patch

ref: e1f94f3ec5a341998d926a70dac6442762d5e3b8
parent: 82f77e914154d4147c149f54c72bc85eef3fcc44
author: Ali Gholami Rudi <ali@rudi.ir>
date: Thu Nov 27 18:22:54 EST 2014

font: do not limit the number of glyphs and rules

NGLYPHS and NGRULES macros are removed.

--- a/Makefile
+++ b/Makefile
@@ -6,7 +6,7 @@
 CFLAGS = -Wall -O2 "-DTROFFFDIR=\"$(FDIR)\"" "-DTROFFMDIR=\"$(MDIR)\""
 LDFLAGS =
 OBJS = roff.o dev.o font.o in.o cp.o tr.o ren.o out.o reg.o sbuf.o fmt.o \
-	eval.o draw.o wb.o hyph.o map.o clr.o char.o dict.o
+	eval.o draw.o wb.o hyph.o map.o clr.o char.o dict.o iset.o
 
 all: roff
 %.o: %.c roff.h
--- a/dict.c
+++ b/dict.c
@@ -3,8 +3,27 @@
 #include <string.h>
 #include "roff.h"
 
-#define DHASH(d, s)	((d)->level2 ? (((unsigned char) (s)[1]) << 8) | (unsigned char) (s)[0] : (unsigned char) s[0])
+#define DHASH(d, s)	((d)->level2 && (s)[0] ? (((unsigned char) (s)[1]) << 8) | (unsigned char) (s)[0] : (unsigned char) s[0])
+#define CNTMIN		(1 << 10)
 
+struct dict {
+	struct iset *map;
+	char **key;
+	long *val;
+	int size;
+	int n;
+	long notfound;		/* the value returned for missing keys */
+	int level2;		/* use two characters for hashing */
+	int dupkeys;		/* duplicate keys if set */
+};
+
+static void dict_extend(struct dict *d, int size)
+{
+	d->key = mextend(d->key, d->size, size, sizeof(d->key[0]));
+	d->val = mextend(d->val, d->size, size, sizeof(d->val[0]));
+	d->size = size;
+}
+
 /*
  * initialise a dictionary
  *
@@ -12,30 +31,29 @@
  * dupkeys: if nonzero, store a copy of keys inserted via dict_put().
  * level2: use two characters for hasing
  */
-void dict_init(struct dict *d, int size, long notfound, int dupkeys, int level2)
+struct dict *dict_make(long notfound, int dupkeys, int level2)
 {
-	int headsize = (level2 ? 256 * 256 : 256) * sizeof(d->head[0]);
+	struct dict *d = xmalloc(sizeof(*d));
 	memset(d, 0, sizeof(*d));
-	d->size = size;
 	d->n = 1;
 	d->level2 = level2;
+	d->dupkeys = dupkeys;
 	d->notfound = notfound;
-	d->key = xmalloc(size * sizeof(d->key[0]));
-	d->val = xmalloc(size * sizeof(d->val[0]));
-	d->next = xmalloc(size * sizeof(d->next[0]));
-	d->head = xmalloc(headsize);
-	memset(d->head, 0, headsize);
-	if (dupkeys)
-		d->buf = xmalloc(size * NMLEN);
+	d->map = iset_make();
+	dict_extend(d, CNTMIN);
+	return d;
 }
 
-void dict_done(struct dict *d)
+void dict_free(struct dict *d)
 {
+	int i;
+	if (d->dupkeys)
+		for (i = 0; i < d->size; i++)
+			free(d->key[i]);
 	free(d->val);
 	free(d->key);
-	free(d->next);
-	free(d->buf);
-	free(d->head);
+	iset_free(d->map);
+	free(d);
 }
 
 void dict_put(struct dict *d, char *key, long val)
@@ -42,30 +60,27 @@
 {
 	int idx;
 	if (d->n >= d->size)
-		return;
-	if (d->buf) {
+		dict_extend(d, d->n + CNTMIN);
+	if (d->dupkeys) {
 		int len = strlen(key) + 1;
-		if (d->buflen + len >= d->size * NMLEN)
-			return;
-		memcpy(d->buf + d->buflen, key, len);
-		key = d->buf + d->buflen;
-		d->buflen += len;
+		char *dup = xmalloc(len);
+		memcpy(dup, key, len);
+		key = dup;
 	}
 	idx = d->n++;
 	d->key[idx] = key;
 	d->val[idx] = val;
-	d->next[idx] = d->head[DHASH(d, key)];
-	d->head[DHASH(d, key)] = idx;
+	iset_put(d->map, DHASH(d, key), idx);
 }
 
 /* return the index of key in d */
 int dict_idx(struct dict *d, char *key)
 {
-	int idx = d->head[DHASH(d, key)];
-	while (idx > 0) {
-		if (!strcmp(d->key[idx], key))
-			return idx;
-		idx = d->next[idx];
+	int *r = iset_get(d->map, DHASH(d, key));
+	while (r && *r >= 0) {
+		if (!strcmp(d->key[*r], key))
+			return *r;
+		r++;
 	}
 	return -1;
 }
@@ -87,20 +102,14 @@
 }
 
 /* match a prefix of key; in the first call, *idx should be -1 */
-long dict_prefix(struct dict *d, char *key, int *idx)
+long dict_prefix(struct dict *d, char *key, int *pos)
 {
-	int plen;
-	if (!*idx)
-		return d->notfound;
-	if (*idx < 0)
-		*idx = d->head[DHASH(d, key)];
-	else
-		*idx = d->next[*idx];
-	while (*idx > 0) {
-		plen = strlen(d->key[*idx]);
-		if (!strncmp(d->key[*idx], key, plen))
-			return d->val[*idx];
-		*idx = d->next[*idx];
+	int *r = iset_get(d->map, DHASH(d, key));
+	while (r && r[++*pos] >= 0) {
+		int idx = r[*pos];
+		int plen = strlen(d->key[idx]);
+		if (!strncmp(d->key[idx], key, plen))
+			return d->val[idx];
 	}
 	return d->notfound;
 }
--- a/font.c
+++ b/font.c
@@ -13,12 +13,6 @@
 #define GF_CON		4	/* context glyph */
 #define GF_GRP		8	/* glyph group */
 
-/* mapping integers to sets */
-static struct iset *iset_make(int keycnt);
-static void iset_free(struct iset *iset);
-static int *iset_get(struct iset *iset, int key);
-static void iset_put(struct iset *iset, int key, int ent);
-
 /* glyph substitution and positioning rules */
 struct grule {
 	struct gpat {			/* rule description */
@@ -36,32 +30,31 @@
 	int spacewid;
 	int special;
 	int cs, cs_ps, bd, zoom;	/* for .cs, .bd, .fzoom requests */
-	struct glyph gl[NGLYPHS];	/* glyphs present in the font */
-	int gl_n;			/* number of glyphs in the font */
-	struct dict gl_dict;		/* mapping from gl[i].id to i */
-	struct dict ch_dict;		/* charset mapping */
-	struct dict ch_map;		/* characters mapped via font_map() */
+	struct glyph *gl;		/* glyphs present in the font */
+	int gl_n, gl_sz;		/* number of glyphs in the font */
+	struct dict *gl_dict;		/* mapping from gl[i].id to i */
+	struct dict *ch_dict;		/* charset mapping */
+	struct dict *ch_map;		/* characters mapped via font_map() */
 	/* font features */
 	char feat_name[NFEATS][8];	/* feature names */
 	int feat_set[NFEATS];		/* feature enabled */
 	int feat_n;
 	/* glyph substitution and positioning */
-	struct grule gsub[NGRULES];	/* glyph substitution rules */
-	int gsub_n;
-	struct grule gpos[NGRULES];	/* glyph positioning rules */
-	int gpos_n;
+	struct grule *gsub;		/* glyph substitution rules */
+	int gsub_n, gsub_sz;
+	struct grule *gpos;		/* glyph positioning rules */
+	int gpos_n, gpos_sz;
 	struct iset *gsub0;		/* rules matching a glyph at pos 0 */
 	struct iset *gpos0;		/* rules matching a glyph at pos 0 */
-	int *ggrp[NGRULES];		/* glyph groups */
-	int ggrp_len[NGRULES];
+	struct iset *ggrp;		/* glyph groups */
 };
 
 /* find a glyph by its name */
 struct glyph *font_find(struct font *fn, char *name)
 {
-	int i = dict_get(&fn->ch_map, name);
+	int i = dict_get(fn->ch_map, name);
 	if (i == -1)
-		i = dict_get(&fn->ch_dict, name);
+		i = dict_get(fn->ch_dict, name);
 	return i >= 0 ? fn->gl + i : NULL;
 }
 
@@ -68,21 +61,24 @@
 /* find a glyph by its device-dependent identifier */
 struct glyph *font_glyph(struct font *fn, char *id)
 {
-	int i = dict_get(&fn->gl_dict, id);
+	int i = dict_get(fn->gl_dict, id);
 	return i >= 0 ? &fn->gl[i] : NULL;
 }
 
-static struct glyph *font_glyphput(struct font *fn, char *id, char *name, int type)
+static int font_glyphput(struct font *fn, char *id, char *name, int type)
 {
-	int i = fn->gl_n++;
 	struct glyph *g;
-	g = &fn->gl[i];
+	if (fn->gl_n == fn->gl_sz) {
+		fn->gl_sz = fn->gl_sz + 1024;
+		fn->gl = mextend(fn->gl, fn->gl_n, fn->gl_sz, sizeof(fn->gl[0]));
+	}
+	g = &fn->gl[fn->gl_n];
 	strcpy(g->id, id);
 	strcpy(g->name, name);
 	g->type = type;
 	g->font = fn;
-	dict_put(&fn->gl_dict, g->id, i);
-	return g;
+	dict_put(fn->gl_dict, g->id, fn->gl_n);
+	return fn->gl_n++;
 }
 
 /* map character name to the given glyph; remove the mapping if id is NULL */
@@ -91,7 +87,7 @@
 	int gidx = -1;
 	if (id)
 		gidx = font_glyph(fn, id) ? font_glyph(fn, id) - fn->gl : -2;
-	dict_put(&fn->ch_map, name, gidx);
+	dict_put(fn->ch_map, name, gidx);
 	return 0;
 }
 
@@ -98,7 +94,7 @@
 /* return nonzero if character name has been mapped with font_map() */
 int font_mapped(struct font *fn, char *name)
 {
-	return dict_get(&fn->ch_map, name) != -1;
+	return dict_get(fn->ch_map, name) != -1;
 }
 
 /* enable/disable ligatures; first bit for liga and the second bit for rlig */
@@ -124,11 +120,12 @@
 
 static int font_gpatmatch(struct font *fn, struct gpat *p, int g)
 {
-	int i;
+	int *r;
 	if (!(p->flg & GF_GRP))
 		return p->g == g;
-	for (i = 0; i < fn->ggrp_len[p->g]; i++)
-		if (fn->ggrp[p->g][i] == g)
+	r = iset_get(fn->ggrp, p->g);
+	while (r && *r >= 0)
+		if (*r++ == g)
 			return 1;
 	return 0;
 }
@@ -285,16 +282,13 @@
 	return ndst;
 }
 
-static int font_readchar(struct font *fn, FILE *fin, int *n, struct glyph **g)
+static int font_readchar(struct font *fn, FILE *fin, int *n, int *gid)
 {
+	struct glyph *g;
 	char tok[ILNLEN];
 	char name[ILNLEN];
 	char id[ILNLEN];
 	int type;
-	if (*n + 1 == NGLYPHS)
-		errmsg("neatroff: NGLYPHS too low\n");
-	if (*n >= NGLYPHS)
-		return 0;
 	if (fscanf(fin, "%s %s", name, tok) != 2)
 		return 1;
 	if (!strcmp("---", name))
@@ -302,14 +296,15 @@
 	if (strcmp("\"", tok)) {
 		if (fscanf(fin, "%d %s", &type, id) != 2)
 			return 1;
-		*g = font_glyph(fn, id);
-		if (!*g) {
-			*g = font_glyphput(fn, id, name, type);
-			sscanf(tok, "%hd,%hd,%hd,%hd,%hd", &(*g)->wid,
-				&(*g)->llx, &(*g)->lly, &(*g)->urx, &(*g)->ury);
+		*gid = dict_get(fn->gl_dict, id);
+		if (*gid < 0) {
+			*gid = font_glyphput(fn, id, name, type);
+			g = &fn->gl[*gid];
+			sscanf(tok, "%hd,%hd,%hd,%hd,%hd", &g->wid,
+				&g->llx, &g->lly, &g->urx, &g->ury);
 		}
 	}
-	dict_put(&fn->ch_dict, name, *g - fn->gl);
+	dict_put(fn->ch_dict, name, *gid);
 	(*n)++;
 	return 0;
 }
@@ -336,10 +331,11 @@
 {
 	struct grule *rule;
 	struct gpat *pats = font_gpat(fn, len);
-	if (fn->gsub_n + 1 == LEN(fn->gsub))
-		errmsg("neatroff: NGRULES too low\n");
-	if (fn->gsub_n >= LEN(fn->gsub) || !pats)
-		return NULL;
+	if (fn->gsub_n  == fn->gsub_sz) {
+		fn->gsub_sz = fn->gsub_sz + 1024;
+		fn->gsub = mextend(fn->gsub, fn->gsub_n, fn->gsub_sz,
+				sizeof(fn->gsub[0]));
+	}
 	rule = &fn->gsub[fn->gsub_n++];
 	rule->pats = pats;
 	rule->len = len;
@@ -351,10 +347,11 @@
 {
 	struct grule *rule;
 	struct gpat *pats = font_gpat(fn, len);
-	if (fn->gpos_n + 1 == LEN(fn->gpos))
-		errmsg("neatroff: NGRULES too low\n");
-	if (fn->gpos_n >= LEN(fn->gpos) || !pats)
-		return NULL;
+	if (fn->gpos_n == fn->gpos_sz) {
+		fn->gpos_sz = fn->gpos_sz + 1024;
+		fn->gpos = mextend(fn->gpos, fn->gpos_n, fn->gpos_sz,
+				sizeof(fn->gpos[0]));
+	}
 	rule = &fn->gpos[fn->gpos_n++];
 	rule->pats = pats;
 	rule->len = len;
@@ -366,8 +363,8 @@
 {
 	if (s[0] == '@') {
 		p->g = atoi(s + 1);
-		if (fn->ggrp_len[p->g] == 1)
-			p->g = fn->ggrp[p->g][0];
+		if (iset_len(fn->ggrp, p->g) == 1)
+			p->g = iset_get(fn->ggrp, p->g)[0];
 		else
 			p->flg |= GF_GRP;
 	} else {
@@ -383,8 +380,7 @@
 	int i, n;
 	if (fscanf(fin, "%s %d", tok, &n) != 2)
 		return 1;
-	if (!(rule = font_gsub(fn, tok, n)))
-		return 0;
+	rule = font_gsub(fn, tok, n);
 	for (i = 0; i < n; i++) {
 		if (fscanf(fin, "%s", tok) != 1)
 			return 1;
@@ -408,8 +404,7 @@
 	int i, n;
 	if (fscanf(fin, "%s %d", tok, &n) != 2)
 		return 1;
-	if (!(rule = font_gpos(fn, tok, n)))
-		return 0;
+	rule = font_gpos(fn, tok, n);
 	for (i = 0; i < n; i++) {
 		if (fscanf(fin, "%s", tok) != 1)
 			return 1;
@@ -433,20 +428,12 @@
 	int id, n, i, g;
 	if (fscanf(fin, "%d %d", &id, &n) != 2)
 		return 1;
-	if (id >= LEN(fn->ggrp)) {
-		errmsg("neatroff: NGRULES too low\n");
-		return 0;
-	}
-	if (fn->ggrp[id])
-		free(fn->ggrp[id]);
-	fn->ggrp[id] = xmalloc(n * sizeof(fn->ggrp[id][0]));
-	fn->ggrp_len[id] = 0;
 	for (i = 0; i < n; i++) {
 		if (fscanf(fin, "%s", tok) != 1)
 			return 1;
 		g = font_idx(fn, font_glyph(fn, tok));
 		if (g >= 0)
-			fn->ggrp[id][fn->ggrp_len[id]++] = g;
+			iset_put(fn->ggrp, id, g);
 	}
 	return 0;
 }
@@ -458,8 +445,7 @@
 	int val;
 	if (fscanf(fin, "%s %s %d", c1, c2, &val) != 3)
 		return 1;
-	if (!(rule = font_gpos(fn, "kern", 2)))
-		return 0;
+	rule = font_gpos(fn, "kern", 2);
 	rule->pats[0].g = font_idx(fn, font_glyph(fn, c1));
 	rule->pats[1].g = font_idx(fn, font_glyph(fn, c2));
 	rule->pats[0].xadv = val;
@@ -477,8 +463,7 @@
 	int j, n = 0;
 	while (utf8read(&s, c) > 0)
 		g[n++] = font_idx(fn, font_find(fn, c));
-	if (!(rule = font_gsub(fn, "liga", n + 1)))
-		return;
+	rule = font_gsub(fn, "liga", n + 1);
 	for (j = 0; j < n; j++) {
 		rule->pats[j].g = g[j];
 		rule->pats[j].flg = GF_PAT;
@@ -506,10 +491,10 @@
 
 static void font_isetinsert(struct font *fn, struct iset *iset, int rule, struct gpat *p)
 {
-	int j;
 	if (p->flg & GF_GRP) {
-		for (j = 0; j < fn->ggrp_len[p->g]; j++)
-			iset_put(iset, fn->ggrp[p->g][j], rule);
+		int *r = iset_get(fn->ggrp, p->g);
+		while (r && *r >= 0)
+			iset_put(iset, *r++, rule);
 	} else {
 		if (p->g >= 0)
 			iset_put(iset, p->g, rule);
@@ -519,7 +504,7 @@
 struct font *font_open(char *path)
 {
 	struct font *fn;
-	struct glyph *ch_g = NULL;	/* last glyph in the charset */
+	int ch_g = -1;		/* last glyph in the charset */
 	int ch_n = 0;			/* number of glyphs in the charset */
 	char tok[ILNLEN];
 	FILE *fin;
@@ -535,9 +520,10 @@
 		return NULL;
 	}
 	memset(fn, 0, sizeof(*fn));
-	dict_init(&fn->gl_dict, NGLYPHS, -1, 0, 0);
-	dict_init(&fn->ch_dict, NGLYPHS, -1, 1, 0);
-	dict_init(&fn->ch_map, NGLYPHS, -1, 1, 0);
+	fn->gl_dict = dict_make(-1, 1, 0);
+	fn->ch_dict = dict_make(-1, 1, 0);
+	fn->ch_map = dict_make(-1, 1, 0);
+	fn->ggrp = iset_make();
 	while (fscanf(fin, "%s", tok) == 1) {
 		if (!strcmp("char", tok)) {
 			font_readchar(fn, fin, &ch_n, &ch_g);
@@ -574,8 +560,8 @@
 	for (i = 0; i < ligs_n; i++)
 		font_lig(fn, ligs[i]);
 	fclose(fin);
-	fn->gsub0 = iset_make(fn->gl_n);
-	fn->gpos0 = iset_make(fn->gl_n);
+	fn->gsub0 = iset_make();
+	fn->gpos0 = iset_make();
 	for (i = 0; i < fn->gsub_n; i++)
 		font_isetinsert(fn, fn->gsub0, i,
 			font_rulefirstpat(fn, &fn->gsub[i]));
@@ -592,13 +578,15 @@
 		free(fn->gsub[i].pats);
 	for (i = 0; i < fn->gpos_n; i++)
 		free(fn->gpos[i].pats);
-	for (i = 0; i < LEN(fn->ggrp); i++)
-		free(fn->ggrp[i]);
-	dict_done(&fn->gl_dict);
-	dict_done(&fn->ch_dict);
-	dict_done(&fn->ch_map);
+	dict_free(fn->gl_dict);
+	dict_free(fn->ch_dict);
+	dict_free(fn->ch_map);
 	iset_free(fn->gsub0);
 	iset_free(fn->gpos0);
+	iset_free(fn->ggrp);
+	free(fn->gsub);
+	free(fn->gpos);
+	free(fn->gl);
 	free(fn);
 }
 
@@ -668,59 +656,4 @@
 	if (idx >= 0)
 		fn->feat_set[idx] = val != 0;
 	return old;
-}
-
-/* iset structure to store a mapping from integers to sets */
-struct iset {
-	int **set;
-	int *sz;
-	int *len;
-	int keycnt;
-};
-
-static struct iset *iset_make(int keycnt)
-{
-	struct iset *iset = malloc(sizeof(*iset));
-	memset(iset, 0, sizeof(*iset));
-	iset->keycnt = keycnt;
-	iset->set = malloc(keycnt * sizeof(iset->set[0]));
-	memset(iset->set, 0, keycnt * sizeof(iset->set[0]));
-	iset->len = malloc(keycnt * sizeof(iset->len[0]));
-	memset(iset->len, 0, keycnt * sizeof(iset->len[0]));
-	iset->sz = malloc(keycnt * sizeof(iset->sz[0]));
-	memset(iset->sz, 0, keycnt * sizeof(iset->sz[0]));
-	return iset;
-}
-
-static void iset_free(struct iset *iset)
-{
-	int i;
-	for (i = 0; i < iset->keycnt; i++)
-		free(iset->set[i]);
-	free(iset->set);
-	free(iset->len);
-	free(iset->sz);
-}
-
-static int *iset_get(struct iset *iset, int key)
-{
-	return key >= 0 && key < iset->keycnt ? iset->set[key] : NULL;
-}
-
-static void iset_put(struct iset *iset, int key, int ent)
-{
-	if (key >= 0 && key < iset->keycnt && iset->len[key] + 1 >= iset->sz[key]) {
-		int olen = iset->sz[key];
-		int nlen = iset->sz[key] * 2 + 8;
-		void *nset = malloc(nlen * sizeof(iset->set[key][0]));
-		if (iset->set[key]) {
-			memcpy(nset, iset->set[key],
-				olen * sizeof(iset->set[key][0]));
-			free(iset->set[key]);
-		}
-		iset->sz[key] = nlen;
-		iset->set[key] = nset;
-	}
-	iset->set[key][iset->len[key]++] = ent;
-	iset->set[key][iset->len[key]] = -1;
 }
--- a/hyph.c
+++ b/hyph.c
@@ -14,7 +14,7 @@
 static char hwword[HYPATLEN];	/* buffer for .hw words */
 static char hwhyph[HYPATLEN];	/* buffer for .hw hyphenations */
 static int hwword_len;		/* used hwword[] length */
-static struct dict hwdict;	/* map words to their index in hwoff[] */
+static struct dict *hwdict;	/* map words to their index in hwoff[] */
 static int hwoff[NHYPHS];	/* the offset of words in hwword[] */
 static int hw_n;		/* the number of dictionary words */
 
@@ -36,7 +36,7 @@
 	}
 	p[i] = '\0';
 	hwoff[hw_n] = hwword_len;
-	dict_put(&hwdict, hwword + hwoff[hw_n], hw_n);
+	dict_put(hwdict, hwword + hwoff[hw_n], hw_n);
 	hwword_len += i + 1;
 	hw_n++;
 }
@@ -48,7 +48,7 @@
 	int map[WORDLEN] = {0};
 	int i, j, idx = -1;
 	hcode_strcpy(word2, word, map, 0);
-	i = dict_prefix(&hwdict, word2, &idx);
+	i = dict_prefix(hwdict, word2, &idx);
 	if (i < 0)
 		return 1;
 	hyph2 = hwhyph + hwoff[i];
@@ -71,7 +71,7 @@
 static char hypats[HYPATLEN];	/* hyphenation patterns */
 static char hynums[HYPATLEN];	/* hyphenation pattern numbers */
 static int hypats_len;		/* used hypats[] and hynums[] length */
-static struct dict hydict;	/* map patterns to their index in hyoff[] */
+static struct dict *hydict;	/* map patterns to their index in hyoff[] */
 static int hyoff[NHYPHS];	/* the offset of this pattern in hypats[] */
 static int hy_n;		/* the number of patterns */
 
@@ -82,7 +82,7 @@
 	char *p, *np;
 	int i, j;
 	int idx = -1;
-	while ((i = dict_prefix(&hydict, s, &idx)) >= 0) {
+	while ((i = dict_prefix(hydict, s, &idx)) >= 0) {
 		p = hypats + hyoff[i];
 		np = hynums + (p - hypats);
 		plen = strlen(p) + 1;
@@ -134,13 +134,13 @@
 	}
 	p[i] = '\0';
 	hyoff[hy_n] = hypats_len;
-	dict_put(&hydict, hypats + hyoff[hy_n], hy_n);
+	dict_put(hydict, hypats + hyoff[hy_n], hy_n);
 	hypats_len += i + 1;
 	hy_n++;
 }
 
 /* .hcode request */
-static struct dict hcodedict;
+static struct dict *hcodedict;
 static char hcodesrc[NHCODES][GNLEN];
 static char hcodedst[NHCODES][GNLEN];
 static int hcode_n;
@@ -148,7 +148,7 @@
 /* replace the character in s after .hcode mapping; returns s's new length */
 static int hcode_mapchar(char *s)
 {
-	int i = dict_get(&hcodedict, s);
+	int i = dict_get(hcodedict, s);
 	if (i >= 0)
 		strcpy(s, hcodedst[i]);
 	else if (isalpha((unsigned char) *s))
@@ -176,13 +176,13 @@
 
 static void hcode_add(char *c1, char *c2)
 {
-	int i = dict_get(&hcodedict, c1);
+	int i = dict_get(hcodedict, c1);
 	if (i >= 0) {
 		strcpy(hcodedst[i], c2);
 	} else if (hcode_n < NHCODES) {
 		strcpy(hcodesrc[hcode_n], c1);
 		strcpy(hcodedst[hcode_n], c2);
-		dict_put(&hcodedict, hcodesrc[hcode_n], hcode_n);
+		dict_put(hcodedict, hcodesrc[hcode_n], hcode_n);
 		hcode_n++;
 	}
 }
@@ -273,9 +273,9 @@
 
 void hyph_init(void)
 {
-	dict_init(&hwdict, NHYPHS, -1, 0, 1);
-	dict_init(&hydict, NHYPHS, -1, 0, 1);
-	dict_init(&hcodedict, NHYPHS, -1, 0, 1);
+	hwdict = dict_make(-1, 0, 1);
+	hydict = dict_make(-1, 0, 1);
+	hcodedict = dict_make(-1, 0, 1);
 }
 
 void tr_hpf(char **args)
@@ -283,14 +283,14 @@
 	/* reseting the patterns */
 	hypats_len = 0;
 	hy_n = 0;
-	dict_done(&hydict);
+	dict_free(hydict);
 	/* reseting the dictionary */
 	hwword_len = 0;
 	hw_n = 0;
-	dict_done(&hwdict);
+	dict_free(hwdict);
 	/* reseting hcode mappings */
 	hcode_n = 0;
-	dict_done(&hcodedict);
+	dict_free(hcodedict);
 	/* reading */
 	hyph_init();
 	tr_hpfa(args);
--- /dev/null
+++ b/iset.c
@@ -1,0 +1,74 @@
+#include <stdlib.h>
+#include <string.h>
+#include "roff.h"
+
+#define ALIGN(n, a)	(((n) + (a) - 1) & ~((a) - 1))
+#define CNTMIN		(1 << 10)
+#define CNTMAX		(1 << 20)
+
+/* iset structure to map integers to sets */
+struct iset {
+	int **set;
+	int *sz;
+	int *len;
+	int cnt;
+};
+
+static void iset_extend(struct iset *iset, int cnt)
+{
+	iset->set = mextend(iset->set, iset->cnt, cnt, sizeof(iset->set[0]));
+	iset->sz = mextend(iset->sz, iset->cnt, cnt, sizeof(iset->sz[0]));
+	iset->len = mextend(iset->len, iset->cnt, cnt, sizeof(iset->len[0]));
+	iset->cnt = cnt;
+}
+
+struct iset *iset_make(void)
+{
+	struct iset *iset = xmalloc(sizeof(*iset));
+	memset(iset, 0, sizeof(*iset));
+	iset_extend(iset, CNTMIN);
+	return iset;
+}
+
+void iset_free(struct iset *iset)
+{
+	int i;
+	for (i = 0; i < iset->cnt; i++)
+		free(iset->set[i]);
+	free(iset->set);
+	free(iset->len);
+	free(iset->sz);
+	free(iset);
+}
+
+int *iset_get(struct iset *iset, int key)
+{
+	return key >= 0 && key < iset->cnt ? iset->set[key] : NULL;
+}
+
+int iset_len(struct iset *iset, int key)
+{
+	return key >= 0 && key < iset->cnt ? iset->len[key] : 0;
+}
+
+void iset_put(struct iset *iset, int key, int ent)
+{
+	if (key < 0 || key >= CNTMAX)
+		return;
+	if (key >= iset->cnt)
+		iset_extend(iset, ALIGN(key + 1, CNTMIN));
+	if (key >= 0 && key < iset->cnt && iset->len[key] + 1 >= iset->sz[key]) {
+		int olen = iset->sz[key];
+		int nlen = iset->sz[key] * 2 + 8;
+		void *nset = xmalloc(nlen * sizeof(iset->set[key][0]));
+		if (iset->set[key]) {
+			memcpy(nset, iset->set[key],
+				olen * sizeof(iset->set[key][0]));
+			free(iset->set[key]);
+		}
+		iset->sz[key] = nlen;
+		iset->set[key] = nset;
+	}
+	iset->set[key][iset->len[key]++] = ent;
+	iset->set[key][iset->len[key]] = -1;
+}
--- a/map.c
+++ b/map.c
@@ -6,8 +6,7 @@
 #define MAPBEG		256	/* the entries reserved for .x names */
 
 /* register, macro, or environments names */
-static struct dict mapdict;
-static int mapinit;
+static struct dict *mapdict;
 
 /* map register names to [0..NREGS] */
 int map(char *s)
@@ -15,14 +14,12 @@
 	int i;
 	if (s[0] == '.' && s[1] && !s[2])	/* ".x" is mapped to 'x' */
 		return (unsigned char) s[1];
-	if (!mapinit) {
-		dict_init(&mapdict, NREGS, -1, 1, 1);
-		mapinit = 1;
-	}
-	i = dict_idx(&mapdict, s);
+	if (!mapdict)
+		mapdict = dict_make(-1, 1, 1);
+	i = dict_idx(mapdict, s);
 	if (i < 0) {
-		dict_put(&mapdict, s, 0);
-		i = dict_idx(&mapdict, s);
+		dict_put(mapdict, s, 0);
+		i = dict_idx(mapdict, s);
 	}
 	return MAPBEG + i;
 }
@@ -32,7 +29,7 @@
 {
 	static char map_buf[NMLEN];
 	if (id >= MAPBEG)
-		return dict_key(&mapdict, id - MAPBEG);
+		return dict_key(mapdict, id - MAPBEG);
 	map_buf[0] = '.';
 	map_buf[1] = id;
 	map_buf[2] = '\0';
--- a/roff.c
+++ b/roff.c
@@ -25,6 +25,15 @@
 	exit(1);
 }
 
+void *mextend(void *old, int oldsz, int newsz, int memsz)
+{
+	void *new = xmalloc(newsz * memsz);
+	memcpy(new, old, oldsz * memsz);
+	memset(new + oldsz * memsz, 0, (newsz - oldsz) * memsz);
+	free(old);
+	return new;
+}
+
 void *xmalloc(long len)
 {
 	void *m = malloc(len);
--- a/roff.h
+++ b/roff.h
@@ -26,7 +26,6 @@
 #define PATHLEN		1024	/* path length */
 #define NFILES		16	/* number of input files */
 #define NFONTS		32	/* number of fonts */
-#define NGLYPHS		1024	/* glyphs in fonts */
 #define FNLEN		32	/* font name length */
 #define NMLEN		32	/* macro/register/environment/glyph name length */
 #define GNLEN		NMLEN	/* glyph name length */
@@ -51,7 +50,6 @@
 #define NHCODES		512	/* number of .hcode characters */
 #define WORDLEN		256	/* word length (for hyph.c) */
 #define NFEATS		128	/* number of features per font */
-#define NGRULES		4096	/* number of gsub/gpos rules per font */
 
 /* converting scales */
 #define SC_IN		(dev_res)	/* inch in units */
@@ -116,22 +114,16 @@
 int tab_next(int pos);
 int tab_type(int pos);
 
-/* dictionary */
-struct dict {
-	int *head;
-	char **key;
-	long *val;
-	int *next;
-	int size;
-	int n;
-	char *buf;		/* buffer for keys */
-	int buflen;
-	int level2;		/* use two characters for hashing */
-	long notfound;		/* the value returned for missing keys */
-};
+/* mapping integers to sets */
+struct iset *iset_make(void);
+void iset_free(struct iset *iset);
+int *iset_get(struct iset *iset, int key);
+void iset_put(struct iset *iset, int key, int ent);
+int iset_len(struct iset *iset, int key);
 
-void dict_init(struct dict *d, int size, long notfound, int dupkeys, int level2);
-void dict_done(struct dict *d);
+/* mapping strings to longs */
+struct dict *dict_make(long notfound, int dupkeys, int level2);
+void dict_free(struct dict *d);
 void dict_put(struct dict *d, char *key, long val);
 long dict_get(struct dict *d, char *key);
 int dict_idx(struct dict *d, char *key);
@@ -386,10 +378,13 @@
 void errmsg(char *msg, ...);
 void errdie(char *msg);
 void *xmalloc(long len);
+void *mextend(void *old, int oldsz, int newsz, int memsz);
+/* utf-8 parsing */
 int utf8len(int c);
 int utf8next(char *s, int (*next)(void));
 int utf8read(char **s, char *d);
 int utf8one(char *s);
+/* reading escapes and characters */
 int charnext(char *c, int (*next)(void), void (*back)(int));
 int charread(char **s, char *c);
 int charnext_delim(char *c, int (*next)(void), void (*back)(int), char *delim);
--- a/tr.c
+++ b/tr.c
@@ -607,7 +607,7 @@
 }
 
 /* character translation (.tr) */
-static struct dict cmap;		/* character mapping */
+static struct dict *cmap;		/* character mapping */
 static char cmap_src[NCMAPS][GNLEN];	/* source character */
 static char cmap_dst[NCMAPS][GNLEN];	/* character mapping */
 static int cmap_n;			/* number of translated character */
@@ -614,13 +614,13 @@
 
 void cmap_add(char *c1, char *c2)
 {
-	int i = dict_get(&cmap, c1);
+	int i = dict_get(cmap, c1);
 	if (i >= 0) {
 		strcpy(cmap_dst[i], c2);
 	} else if (cmap_n < NCMAPS) {
 		strcpy(cmap_src[cmap_n], c1);
 		strcpy(cmap_dst[cmap_n], c2);
-		dict_put(&cmap, cmap_src[cmap_n], cmap_n);
+		dict_put(cmap, cmap_src[cmap_n], cmap_n);
 		cmap_n++;
 	}
 }
@@ -627,7 +627,7 @@
 
 char *cmap_map(char *c)
 {
-	int i = dict_get(&cmap, c);
+	int i = dict_get(cmap, c);
 	return i >= 0 ? cmap_dst[i] : c;
 }
 
@@ -1077,5 +1077,5 @@
 	int i;
 	for (i = 0; i < LEN(cmds); i++)
 		str_dset(map(cmds[i].id), &cmds[i]);
-	dict_init(&cmap, NCMAPS, -1, 0, 0);
+	cmap = dict_make(-1, 0, 0);
 }