shithub: fnt

Download patch

ref: 094b3e1d61c9c7cd85c3dfba8a6b40185004eed3
parent: a100cd99db8f71ec9d1caeb4d16b65f1f7d0a693
author: Sigrid Solveig Haflínudóttir <sigrid@ftrv.se>
date: Wed Jul 24 13:16:47 EDT 2024

initial bitmap fonts support

--- a/otf.c.in
+++ b/otf.c.in
@@ -236,6 +236,34 @@
 	return -1;
 }
 
+void
+print_BitmapGlyph(Otfile *f, int indent, Otf *o, BitmapGlyph *v)
+{
+	void *a = f->aux;
+	int i;
+
+	USED(o);
+	if(v->bitDepth > 0)
+		f->print(a, "%*s%s: %d\n", indent, "", "bitDepth", v->bitDepth);
+	f->print(a, "%*s%s: %d\n", indent, "", "format", v->format);
+	f->print(a, "%*s%s: %d\n", indent, "", "offset", v->offset);
+	if(v->size > 0){
+		f->print(a, "%*s%s: %d\n", indent, "", "size", v->size);
+		f->print(a, "%*s%s: ", indent, "", "image");
+		for(i = 0; i < v->size; i++)
+			f->print(a, "%02"PRIx8, v->image[i]);
+		f->print(a, "\n");
+	}
+	if(v->numComponents > 0){
+		f->print(a, "%*s%s: %d\n", indent, "", "numComponents");
+		f->print(a, "%*s%s:\n", indent, "", "components");
+		for(i = 0; i < v->numComponents; i++){
+			f->print(a, "%*s%d:\n", indent+indentΔ, "", i);
+			print_EbdtComponent(f, indent+indentΔ+indentΔ, o, v->components+i);
+		}
+	}
+}
+
 enum {
 	CGLYPH_FL_WORD_ARGS = 1<<0,
 	CGLYPH_FL_SCALE = 1<<3,
@@ -519,16 +547,198 @@
 	}
 }
 
+static Glyf *
+bitebdt(Otf *o, Glyf *g, BitmapGlyph *bg)
+{
+	u8int *b;
+	int i;
+
+	if(o->ebdt == nil){
+		for(i = 0; i < o->td.numTables; i++){
+			TableRecord *rec = o->td.tableRecords+i;
+			if(rec->tableTag == (u32int)('E'<<24|'B'<<16|'D'<<8|'T')){
+				o->ebdt = rec;
+				break;
+			}
+		}
+		if(o->ebdt == nil){
+			werrstr("no EBDT table");
+			goto err;
+		}
+	}
+
+	if(otfpushrange(o, o->ebdt->offset, o->ebdt->length) < 0)
+		goto err;
+	if(otfpushrange(o, bg->offset, bg->size) < 0)
+		goto err;
+	if(bg->format == 1 || bg->format == 2 || bg->format == 8){
+		SmallGlyphMetrics sm;
+		if(read_SmallGlyphMetrics(o, &sm) < 0)
+			goto err;
+		g->xMin = sm.bearingX;
+		g->yMin = sm.bearingY - sm.height;
+		g->xMax = sm.bearingX + sm.width;
+		g->yMax = sm.bearingY;
+		bg->size -= 6;
+	}else if(bg->format == 6 || bg->format == 7 || bg->format == 9){
+		BigGlyphMetrics bm;
+		if(read_BigGlyphMetrics(o, &bm) < 0)
+			goto err;
+		/* FIXME only horizontal metrics, also this is probably wrong */
+		g->xMax = bm.width;
+		g->yMax = bm.height;
+		g->xMin = -(int)bm.horiBearingX;
+		g->yMin = -(int)bm.horiBearingY;
+		g->xMax += g->xMin;
+		g->yMax += g->yMin;
+		bg->size -= 8;
+	}
+
+	bg->numComponents = 0;
+	switch(bg->format){
+	case 8:
+		if(otfreadn(o, 1) == nil) /* skip uint8 pad */
+			goto err;
+		bg->size -= 1;
+		/* fallthrough */
+	case 9:
+		if((b = otfreadn(o, 2)) == nil)
+			goto err;
+		bg->numComponents = b[0]<<8 | b[1];
+		bg->size -= 2;
+		if(otfarray(o, &bg->components, read_EbdtComponent, sizeof(EbdtComponent), bg->numComponents) < 0)
+			goto err;
+		bg->size -= 4*bg->numComponents;
+		/* fallthrough */
+	case 1:
+	case 2:
+	case 5:
+	case 6:
+	case 7:
+		if((b = otfreadn(o, bg->size)) == nil)
+			goto err;
+		g->bitmap = malloc(sizeof(*bg) + bg->size);
+		memcpy(g->bitmap, bg, sizeof(*bg));
+		memcpy(g->bitmap->image, b, bg->size);
+	}
+	otfpoprange(o);
+	otfpoprange(o);
+
+	return g;
+err:
+	free(g->bitmap);
+	free(g);
+	otfpopranges(o);
+	return nil;
+}
+
+static Glyf *
+bitglyf(Otf *o, Glyf *g)
+{
+	IndexSubtableRecord *isr;
+	IndexSubtable *is;
+	BitmapSize *bs;
+	int i, j, k, n;
+	BitmapGlyph b;
+
+	g->type = GLYPH_BITMAP;
+	bs = o->td.eblc->bitmapSizes;
+	for(i = 0; i < (int)o->td.eblc->numSizes; i++, bs++){
+		if(g->index >= bs->startGlyphIndex && g->index <= bs->endGlyphIndex){
+			b.bitDepth = bs->bitDepth;
+			isr = bs->indexSubtableList;
+			for(j = 0; j < (int)bs->numberOfIndexSubtables; j++){
+				if(g->index >= isr->firstGlyphIndex && g->index <= isr->lastGlyphIndex){
+					is = isr->indexSubtable;
+					if(is == nil)
+						break;
+					b.format = is->imageFormat;
+					b.offset = is->imageDataOffset;
+					n = g->index - isr->firstGlyphIndex;
+					switch(is->indexFormat){
+					case 1:
+						b.offset += is->sub1.sbitOffsets[n];
+						b.size = is->sub1.sbitOffsets[n+1] - is->sub1.sbitOffsets[n];
+						break;
+					case 2:
+						b.offset += n*is->sub2.imageSize;
+						b.size = is->sub2.imageSize;
+						/* FIXME - vertical metrics are not used here, also this is probably wrong */
+						g->xMin = -is->sub2.bigMetrics.horiBearingX;
+						g->yMin = is->sub2.bigMetrics.horiBearingY - is->sub2.bigMetrics.height;
+						g->xMax = is->sub2.bigMetrics.width + g->xMin;
+						g->yMax = is->sub2.bigMetrics.height + g->yMin;
+						break;
+					case 3:
+						b.offset += is->sub3.sbitOffsets[n];
+						b.size = is->sub3.sbitOffsets[n+1] - is->sub3.sbitOffsets[n];
+						break;
+					case 4:
+						n = is->sub4.numGlyphs;
+						for(k = 0; k < n; k++){
+							if(is->sub4.glyphArray[k].glyphID == g->index)
+								break;
+						}
+						if(k >= n){
+							werrstr("not in glyph array");
+							goto err;
+						}
+						b.offset += is->sub4.glyphArray[k].sbitOffset;
+						b.size = is->sub4.glyphArray[k+1].sbitOffset - is->sub4.glyphArray[k].sbitOffset;
+						break;
+					case 5:
+						n = is->sub5.numGlyphs;
+						for(k = 0; k < n; k++){
+							if(is->sub5.glyphIdArray[k] == g->index)
+								break;
+						}
+						if(k >= n){
+							werrstr("not in glyph id array");
+							goto err;
+						}
+						b.offset += k*is->sub5.imageSize;
+						b.size = is->sub5.imageSize;
+						/* FIXME - vertical metrics are not used here, also this is probably wrong */
+						g->xMin = -is->sub5.bigMetrics.horiBearingX;
+						g->yMin = is->sub5.bigMetrics.horiBearingY - is->sub5.bigMetrics.height;
+						g->xMax = is->sub5.bigMetrics.width + g->xMin;
+						g->yMax = is->sub5.bigMetrics.height + g->yMin;
+						break;
+					default:
+						werrstr("unsupported index format: %d", is->indexFormat);
+						goto err;
+					}
+
+					return bitebdt(o, g, &b);
+				}
+			}
+		}
+	}
+
+	werrstr("no bitmap");
+err:
+	free(g);
+	return nil;
+}
+
 Glyf *
 otfglyf(Otf *o, int index)
 {
-	Glyf *g;
 	int off, len, i;
+	Glyf *g;
 
 	if((g = calloc(1, sizeof(*g))) == nil){
 		werrstr("no memory");
 		goto err;
 	}
+	if(index < 0 || index >= o->numGlyphs){
+		werrstr("index out of range");
+		goto err;
+	}
+	g->index = index;
+	if(o->td.eblc != nil && o->td.ebdt != nil)
+		return bitglyf(o, g);
+
 	if(o->td.head == nil){
 		werrstr("no head table");
 		goto err;
@@ -537,23 +747,19 @@
 		werrstr("no loca table");
 		goto err;
 	}
-	if(o->glyf.offset == 0){
+	if(o->glyf == nil){
 		for(i = 0; i < o->td.numTables; i++){
 			TableRecord *rec = o->td.tableRecords+i;
 			if(rec->tableTag == (u32int)('g'<<24|'l'<<16|'y'<<8|'f')){
-				o->glyf = *rec;
+				o->glyf = rec;
 				break;
 			}
 		}
-		if(o->glyf.offset == 0){
+		if(o->glyf == nil){
 			werrstr("no glyf table");
 			goto err;
 		}
 	}
-	if(index < 0 || index >= o->numGlyphs){
-		werrstr("index out of range");
-		goto err;
-	}
 	if(o->indexToLocFormat == 0){
 		off = (int)o->td.loca->shortOffsets[index]*2;
 		len = (int)o->td.loca->shortOffsets[index+1]*2 - off;
@@ -564,7 +770,7 @@
 	if(len < 1) /* no outlines */
 		return g;
 
-	if(otfpushrange(o, o->glyf.offset, o->glyf.length) < 0)
+	if(otfpushrange(o, o->glyf->offset, o->glyf->length) < 0)
 		goto err;
 	if(otfpushrange(o, off, len) < 0)
 		goto err;
@@ -574,6 +780,7 @@
 	}
 	otfpoprange(o);
 	otfpoprange(o);
+	g->type = g->numberOfContours < 0 ? GLYPH_COMPONENT : GLYPH_SIMPLE;
 
 	return g;
 err:
--- a/otf.h.in
+++ b/otf.h.in
@@ -36,6 +36,26 @@
 
 #define NoRune (~(Rune)0)
 
+enum {
+	GLYPH_EMPTY,
+	GLYPH_SIMPLE,
+	GLYPH_COMPONENT,
+	GLYPH_BITMAP,
+};
+
+typedef struct BitmapGlyph BitmapGlyph;
+typedef struct EbdtComponent EbdtComponent;
+
+struct BitmapGlyph {
+	int bitDepth;
+	int format;
+	int offset;
+	int size;
+	int numComponents;
+	EbdtComponent *components;
+	u8int image[];
+};
+
 typedef struct ComponentGlyph ComponentGlyph;
 
 enum {
--- a/otf.rkt
+++ b/otf.rkt
@@ -201,6 +201,11 @@
          {uint16 numberOfHMetrics ->o}
          #:tag "hhea")
 
+(mkcmplx EbdtComponent
+  {uint16 glyphID}
+  {int8 xOffset}
+  {int8 yOffset})
+
 (mkcmplx
  Glyf
  {int16 numberOfContours ->o}
@@ -209,17 +214,32 @@
  {int16 xMax}
  {int16 yMax}
  #:extra
- (list (cons 'field (list (~a "ComponentGlyph *component;") (~a "SimpleGlyph *simple;")))
+ (list (cons 'field (list (~a "int index;")
+                          (~a "int type;")
+                          (~a "union {");
+                          (~a "	ComponentGlyph *component;")
+                          (~a "	SimpleGlyph *simple;")
+                          (~a "	BitmapGlyph *bitmap;")
+                          (~a "};")))
        (cons 'read
-             (list (~a "if(v->numberOfContours < 0 && read_ComponentGlyph(o, &v->component, 0) < 0)")
-                   (~a "	goto err;")
-                   (~a "if(v->numberOfContours > 0 && read_SimpleGlyph(o, &v->simple) < 0)")
-                   (~a "	goto err;")))
-       (cons 'print
-             (list (~a "if(v->component != nil){")
+             (list (~a "if(v->numberOfContours < 0){")
+                   (~a "	if(read_ComponentGlyph(o, &v->component, 0) < 0)")
+                   (~a "		goto err;")
+                   (~a "	v->type = GLYPH_COMPONENT;")
+                   (~a "}else if(v->numberOfContours > 0){")
+                   (~a "	if(read_SimpleGlyph(o, &v->simple) < 0)")
+                   (~a "		goto err;")
+                   (~a "	v->type = GLYPH_SIMPLE;")
+                   (~a "}else if(o->td.eblc != nil && o->td.ebdt != nil)")
+                   (~a "	v->type = GLYPH_BITMAP;")))
+        (cons 'print
+             (list (~a "if(v->type == GLYPH_BITMAP){")
+                   (~a "	f->print(f->aux, \"%*s%s:\\n\", indent, \"\", \"bitmap\");")
+                   (~a "	print_BitmapGlyph(f, indent+indentΔ, o, v->bitmap);")
+                   (~a "}else if(v->type == GLYPH_COMPONENT){")
                    (~a "	f->print(f->aux, \"%*s%s:\\n\", indent, \"\", \"component\");")
                    (~a "	print_ComponentGlyph(f, indent+indentΔ, o, v->component);")
-                   (~a "}else if(v->simple != nil){")
+                   (~a "}else if(v->type == GLYPH_SIMPLE){")
                    (~a "	f->print(f->aux, \"%*s%s:\\n\", indent, \"\", \"simple\");")
                    (~a "	print_SimpleGlyph(f, indent+indentΔ, o, v->simple);")
                    (~a "}")))))
@@ -347,7 +367,7 @@
  {uint16 endGlyphIndex}
  {uint8 ppemX}
  {uint8 ppemY}
- {uint8 bitDepth}
+ {uint8 bitDepth (== 1 2 4 8)}
  {int8 flags hex (bits bitmapSizeFlags)}
  {IndexSubtableRecord indexSubtableList [numberOfIndexSubtables] (at indexSubtableListOffset)})
 
--- a/otfpriv.h.in
+++ b/otfpriv.h.in
@@ -9,7 +9,8 @@
 
 	/* extra fields to simplify parsing */
 	TableDirectory td;
-	TableRecord glyf;
+	TableRecord *glyf;
+	TableRecord *ebdt;
 OTF_EXTRA_FIELDS
 };
 
--- a/plan9/otf.c
+++ b/plan9/otf.c
@@ -237,6 +237,34 @@
 	return -1;
 }
 
+void
+print_BitmapGlyph(Otfile *f, int indent, Otf *o, BitmapGlyph *v)
+{
+	void *a = f->aux;
+	int i;
+
+	USED(o);
+	if(v->bitDepth > 0)
+		f->print(a, "%*s%s: %d\n", indent, "", "bitDepth", v->bitDepth);
+	f->print(a, "%*s%s: %d\n", indent, "", "format", v->format);
+	f->print(a, "%*s%s: %d\n", indent, "", "offset", v->offset);
+	if(v->size > 0){
+		f->print(a, "%*s%s: %d\n", indent, "", "size", v->size);
+		f->print(a, "%*s%s: ", indent, "", "image");
+		for(i = 0; i < v->size; i++)
+			f->print(a, "%02"PRIx8, v->image[i]);
+		f->print(a, "\n");
+	}
+	if(v->numComponents > 0){
+		f->print(a, "%*s%s: %d\n", indent, "", "numComponents");
+		f->print(a, "%*s%s:\n", indent, "", "components");
+		for(i = 0; i < v->numComponents; i++){
+			f->print(a, "%*s%d:\n", indent+indentΔ, "", i);
+			print_EbdtComponent(f, indent+indentΔ+indentΔ, o, v->components+i);
+		}
+	}
+}
+
 enum {
 	CGLYPH_FL_WORD_ARGS = 1<<0,
 	CGLYPH_FL_SCALE = 1<<3,
@@ -520,16 +548,198 @@
 	}
 }
 
+static Glyf *
+bitebdt(Otf *o, Glyf *g, BitmapGlyph *bg)
+{
+	u8int *b;
+	int i;
+
+	if(o->ebdt == nil){
+		for(i = 0; i < o->td.numTables; i++){
+			TableRecord *rec = o->td.tableRecords+i;
+			if(rec->tableTag == (u32int)('E'<<24|'B'<<16|'D'<<8|'T')){
+				o->ebdt = rec;
+				break;
+			}
+		}
+		if(o->ebdt == nil){
+			werrstr("no EBDT table");
+			goto err;
+		}
+	}
+
+	if(otfpushrange(o, o->ebdt->offset, o->ebdt->length) < 0)
+		goto err;
+	if(otfpushrange(o, bg->offset, bg->size) < 0)
+		goto err;
+	if(bg->format == 1 || bg->format == 2 || bg->format == 8){
+		SmallGlyphMetrics sm;
+		if(read_SmallGlyphMetrics(o, &sm) < 0)
+			goto err;
+		g->xMin = sm.bearingX;
+		g->yMin = sm.bearingY - sm.height;
+		g->xMax = sm.bearingX + sm.width;
+		g->yMax = sm.bearingY;
+		bg->size -= 6;
+	}else if(bg->format == 6 || bg->format == 7 || bg->format == 9){
+		BigGlyphMetrics bm;
+		if(read_BigGlyphMetrics(o, &bm) < 0)
+			goto err;
+		/* FIXME only horizontal metrics, also this is probably wrong */
+		g->xMax = bm.width;
+		g->yMax = bm.height;
+		g->xMin = -(int)bm.horiBearingX;
+		g->yMin = -(int)bm.horiBearingY;
+		g->xMax += g->xMin;
+		g->yMax += g->yMin;
+		bg->size -= 8;
+	}
+
+	bg->numComponents = 0;
+	switch(bg->format){
+	case 8:
+		if(otfreadn(o, 1) == nil) /* skip uint8 pad */
+			goto err;
+		bg->size -= 1;
+		/* fallthrough */
+	case 9:
+		if((b = otfreadn(o, 2)) == nil)
+			goto err;
+		bg->numComponents = b[0]<<8 | b[1];
+		bg->size -= 2;
+		if(otfarray(o, &bg->components, read_EbdtComponent, sizeof(EbdtComponent), bg->numComponents) < 0)
+			goto err;
+		bg->size -= 4*bg->numComponents;
+		/* fallthrough */
+	case 1:
+	case 2:
+	case 5:
+	case 6:
+	case 7:
+		if((b = otfreadn(o, bg->size)) == nil)
+			goto err;
+		g->bitmap = malloc(sizeof(*bg) + bg->size);
+		memcpy(g->bitmap, bg, sizeof(*bg));
+		memcpy(g->bitmap->image, b, bg->size);
+	}
+	otfpoprange(o);
+	otfpoprange(o);
+
+	return g;
+err:
+	free(g->bitmap);
+	free(g);
+	otfpopranges(o);
+	return nil;
+}
+
+static Glyf *
+bitglyf(Otf *o, Glyf *g)
+{
+	IndexSubtableRecord *isr;
+	IndexSubtable *is;
+	BitmapSize *bs;
+	int i, j, k, n;
+	BitmapGlyph b;
+
+	g->type = GLYPH_BITMAP;
+	bs = o->td.eblc->bitmapSizes;
+	for(i = 0; i < (int)o->td.eblc->numSizes; i++, bs++){
+		if(g->index >= bs->startGlyphIndex && g->index <= bs->endGlyphIndex){
+			b.bitDepth = bs->bitDepth;
+			isr = bs->indexSubtableList;
+			for(j = 0; j < (int)bs->numberOfIndexSubtables; j++){
+				if(g->index >= isr->firstGlyphIndex && g->index <= isr->lastGlyphIndex){
+					is = isr->indexSubtable;
+					if(is == nil)
+						break;
+					b.format = is->imageFormat;
+					b.offset = is->imageDataOffset;
+					n = g->index - isr->firstGlyphIndex;
+					switch(is->indexFormat){
+					case 1:
+						b.offset += is->sub1.sbitOffsets[n];
+						b.size = is->sub1.sbitOffsets[n+1] - is->sub1.sbitOffsets[n];
+						break;
+					case 2:
+						b.offset += n*is->sub2.imageSize;
+						b.size = is->sub2.imageSize;
+						/* FIXME - vertical metrics are not used here, also this is probably wrong */
+						g->xMin = -is->sub2.bigMetrics.horiBearingX;
+						g->yMin = is->sub2.bigMetrics.horiBearingY - is->sub2.bigMetrics.height;
+						g->xMax = is->sub2.bigMetrics.width + g->xMin;
+						g->yMax = is->sub2.bigMetrics.height + g->yMin;
+						break;
+					case 3:
+						b.offset += is->sub3.sbitOffsets[n];
+						b.size = is->sub3.sbitOffsets[n+1] - is->sub3.sbitOffsets[n];
+						break;
+					case 4:
+						n = is->sub4.numGlyphs;
+						for(k = 0; k < n; k++){
+							if(is->sub4.glyphArray[k].glyphID == g->index)
+								break;
+						}
+						if(k >= n){
+							werrstr("not in glyph array");
+							goto err;
+						}
+						b.offset += is->sub4.glyphArray[k].sbitOffset;
+						b.size = is->sub4.glyphArray[k+1].sbitOffset - is->sub4.glyphArray[k].sbitOffset;
+						break;
+					case 5:
+						n = is->sub5.numGlyphs;
+						for(k = 0; k < n; k++){
+							if(is->sub5.glyphIdArray[k] == g->index)
+								break;
+						}
+						if(k >= n){
+							werrstr("not in glyph id array");
+							goto err;
+						}
+						b.offset += k*is->sub5.imageSize;
+						b.size = is->sub5.imageSize;
+						/* FIXME - vertical metrics are not used here, also this is probably wrong */
+						g->xMin = -is->sub5.bigMetrics.horiBearingX;
+						g->yMin = is->sub5.bigMetrics.horiBearingY - is->sub5.bigMetrics.height;
+						g->xMax = is->sub5.bigMetrics.width + g->xMin;
+						g->yMax = is->sub5.bigMetrics.height + g->yMin;
+						break;
+					default:
+						werrstr("unsupported index format: %d", is->indexFormat);
+						goto err;
+					}
+
+					return bitebdt(o, g, &b);
+				}
+			}
+		}
+	}
+
+	werrstr("no bitmap");
+err:
+	free(g);
+	return nil;
+}
+
 Glyf *
 otfglyf(Otf *o, int index)
 {
-	Glyf *g;
 	int off, len, i;
+	Glyf *g;
 
 	if((g = calloc(1, sizeof(*g))) == nil){
 		werrstr("no memory");
 		goto err;
 	}
+	if(index < 0 || index >= o->numGlyphs){
+		werrstr("index out of range");
+		goto err;
+	}
+	g->index = index;
+	if(o->td.eblc != nil && o->td.ebdt != nil)
+		return bitglyf(o, g);
+
 	if(o->td.head == nil){
 		werrstr("no head table");
 		goto err;
@@ -538,23 +748,19 @@
 		werrstr("no loca table");
 		goto err;
 	}
-	if(o->glyf.offset == 0){
+	if(o->glyf == nil){
 		for(i = 0; i < o->td.numTables; i++){
 			TableRecord *rec = o->td.tableRecords+i;
 			if(rec->tableTag == (u32int)('g'<<24|'l'<<16|'y'<<8|'f')){
-				o->glyf = *rec;
+				o->glyf = rec;
 				break;
 			}
 		}
-		if(o->glyf.offset == 0){
+		if(o->glyf == nil){
 			werrstr("no glyf table");
 			goto err;
 		}
 	}
-	if(index < 0 || index >= o->numGlyphs){
-		werrstr("index out of range");
-		goto err;
-	}
 	if(o->indexToLocFormat == 0){
 		off = (int)o->td.loca->shortOffsets[index]*2;
 		len = (int)o->td.loca->shortOffsets[index+1]*2 - off;
@@ -565,7 +771,7 @@
 	if(len < 1) /* no outlines */
 		return g;
 
-	if(otfpushrange(o, o->glyf.offset, o->glyf.length) < 0)
+	if(otfpushrange(o, o->glyf->offset, o->glyf->length) < 0)
 		goto err;
 	if(otfpushrange(o, off, len) < 0)
 		goto err;
@@ -575,6 +781,7 @@
 	}
 	otfpoprange(o);
 	otfpoprange(o);
+	g->type = g->numberOfContours < 0 ? GLYPH_COMPONENT : GLYPH_SIMPLE;
 
 	return g;
 err:
@@ -1814,6 +2021,30 @@
 }
 
 int
+read_EbdtComponent(Otf *o, EbdtComponent *v)
+{
+	u8int *b = nil; USED(b);
+	if((b = otfreadn(o, 4)) == nil)
+		goto err;
+	v->glyphID = b[0]<<8 | b[1];
+	v->xOffset = b[2];
+	v->yOffset = b[3];
+	return 0;
+err:
+	werrstr("%s: %r", "EbdtComponent");
+	return -1;
+}
+
+void
+print_EbdtComponent(Otfile *f, int indent, Otf *o, EbdtComponent *v)
+{
+	f->print(f->aux, "%*s%s: %ud\n", indent, "", "glyphID", v->glyphID);
+	f->print(f->aux, "%*s%s: %d\n", indent, "", "xOffset", v->xOffset);
+	f->print(f->aux, "%*s%s: %d\n", indent, "", "yOffset", v->yOffset);
+	USED(o);
+}
+
+int
 read_Glyf(Otf *o, Glyf *v)
 {
 	u8int *b = nil; USED(b);
@@ -1825,10 +2056,16 @@
 	v->yMin = b[4]<<8 | b[5];
 	v->xMax = b[6]<<8 | b[7];
 	v->yMax = b[8]<<8 | b[9];
-	if(v->numberOfContours < 0 && read_ComponentGlyph(o, &v->component, 0) < 0)
-		goto err;
-	if(v->numberOfContours > 0 && read_SimpleGlyph(o, &v->simple) < 0)
-		goto err;
+	if(v->numberOfContours < 0){
+		if(read_ComponentGlyph(o, &v->component, 0) < 0)
+			goto err;
+		v->type = GLYPH_COMPONENT;
+	}else if(v->numberOfContours > 0){
+		if(read_SimpleGlyph(o, &v->simple) < 0)
+			goto err;
+		v->type = GLYPH_SIMPLE;
+	}else if(o->td.eblc != nil && o->td.ebdt != nil)
+		v->type = GLYPH_BITMAP;
 	return 0;
 err:
 	werrstr("%s: %r", "Glyf");
@@ -1843,10 +2080,13 @@
 	f->print(f->aux, "%*s%s: %d\n", indent, "", "yMin", v->yMin);
 	f->print(f->aux, "%*s%s: %d\n", indent, "", "xMax", v->xMax);
 	f->print(f->aux, "%*s%s: %d\n", indent, "", "yMax", v->yMax);
-	if(v->component != nil){
+	if(v->type == GLYPH_BITMAP){
+		f->print(f->aux, "%*s%s:\n", indent, "", "bitmap");
+		print_BitmapGlyph(f, indent+indentΔ, o, v->bitmap);
+	}else if(v->type == GLYPH_COMPONENT){
 		f->print(f->aux, "%*s%s:\n", indent, "", "component");
 		print_ComponentGlyph(f, indent+indentΔ, o, v->component);
-	}else if(v->simple != nil){
+	}else if(v->type == GLYPH_SIMPLE){
 		f->print(f->aux, "%*s%s:\n", indent, "", "simple");
 		print_SimpleGlyph(f, indent+indentΔ, o, v->simple);
 	}
@@ -2495,6 +2735,10 @@
 	v->ppemX = b[4];
 	v->ppemY = b[5];
 	v->bitDepth = b[6];
+	if(v->bitDepth != 1 && v->bitDepth != 2 && v->bitDepth != 4 && v->bitDepth != 8){
+		werrstr("%s: invalid value: %d (%#ux)", "bitDepth", v->bitDepth, v->bitDepth);
+		goto err;
+	}
 	v->flags = b[7];
 	if(v->indexSubtableListOffset != 0){
 		if(otfpushrange(o, v->indexSubtableListOffset, -1) < 0)
--- a/plan9/otf.h
+++ b/plan9/otf.h
@@ -37,6 +37,26 @@
 
 #define NoRune (~(Rune)0)
 
+enum {
+	GLYPH_EMPTY,
+	GLYPH_SIMPLE,
+	GLYPH_COMPONENT,
+	GLYPH_BITMAP,
+};
+
+typedef struct BitmapGlyph BitmapGlyph;
+typedef struct EbdtComponent EbdtComponent;
+
+struct BitmapGlyph {
+	int bitDepth;
+	int format;
+	int offset;
+	int size;
+	int numComponents;
+	EbdtComponent *components;
+	u8int image[];
+};
+
 typedef struct ComponentGlyph ComponentGlyph;
 
 enum {
@@ -132,6 +152,7 @@
 typedef struct TableCmap TableCmap;
 typedef struct TableHead TableHead;
 typedef struct TableHhea TableHhea;
+typedef struct EbdtComponent EbdtComponent;
 typedef struct Glyf Glyf;
 typedef struct LongHorMetric LongHorMetric;
 typedef struct TableMaxp TableMaxp;
@@ -476,6 +497,15 @@
 int read_TableHhea(Otf *o, TableHhea *v);
 void print_TableHhea(Otfile *f, int indent, Otf *o, TableHhea *v);
 
+struct EbdtComponent {
+	u16int glyphID;
+	s8int xOffset;
+	s8int yOffset;
+};
+
+int read_EbdtComponent(Otf *o, EbdtComponent *v);
+void print_EbdtComponent(Otfile *f, int indent, Otf *o, EbdtComponent *v);
+
 struct Glyf {
 	s16int numberOfContours;
 	s16int xMin;
@@ -482,8 +512,13 @@
 	s16int yMin;
 	s16int xMax;
 	s16int yMax;
-	ComponentGlyph *component;
-	SimpleGlyph *simple;
+	int index;
+	int type;
+	union {
+		ComponentGlyph *component;
+		SimpleGlyph *simple;
+		BitmapGlyph *bitmap;
+	};
 };
 
 int read_Glyf(Otf *o, Glyf *v);
--- a/plan9/otfpriv.h
+++ b/plan9/otfpriv.h
@@ -10,7 +10,8 @@
 
 	/* extra fields to simplify parsing */
 	TableDirectory td;
-	TableRecord glyf;
+	TableRecord *glyf;
+	TableRecord *ebdt;
 	s16int indexToLocFormat;
 	u16int numberOfHMetrics;
 	s16int numberOfContours;
@@ -26,7 +27,7 @@
 	u16int designAxisSize;
 	u16int designAxisCount;
 	u16int axisValueCount;
-#line 14"otfpriv.h.in"
+#line 15"otfpriv.h.in"
 };
 
 struct Range {
--- a/rast.c
+++ b/rast.c
@@ -547,11 +547,11 @@
 	return npts < 2 ? 0 : npts;
 }
 
-GlyfImage *
-otfdrawglyf(Otf *o, Glyf *g, double ppem, double scaleX, double scaleY)
+static GlyfImage *
+outlineglyf(Otf *o, Glyf *g, double ppem, double scaleX, double scaleY)
 {
 	int i, j, maxptstotal, maxpts, ngs, w, h, npx, baseline;
-	Glyf *gs[MAXCOMPONENTS];
+	Glyf *gs[MAXCOMPONENTS] = {nil};
 	ComponentGlyph *cg;
 	SegQ *s₀, *s, *p;
 	GlyfImage *im;
@@ -679,4 +679,69 @@
 	}
 	free(pts);
 	return im;
+}
+
+GlyfImage *
+bitmapglyf(Otf *o, Glyf *g, double ppem, double scaleX, double scaleY)
+{
+	int zoomX, zoomY, w, h, x, y, i;
+	BitmapGlyph *bg;
+	GlyfImage *im;
+	u8int *b, *p;
+
+	USED(o);
+	bg = g->bitmap;
+	if(bg->numComponents > 0){
+		/* FIXME implement this. components may also be outlines */
+		werrstr("components not implemented");
+		return nil;
+	}
+
+	if((zoomX = scaleX * ppem / 72) < 1)
+		zoomX = 1;
+	if((zoomY = scaleY * ppem / 72) < 1)
+		zoomY = 1;
+
+	w = g->xMax - g->xMin;
+	h = g->yMax - g->yMin;
+	im = calloc(1, sizeof(*im) + w*zoomX*h*zoomY);
+	im->w = w*zoomX;
+	im->h = h*zoomY;
+	im->baseline = g->yMin * zoomY;
+	b = im->b;
+	if(bg->bitDepth == 1){
+		for(y = 0; y < h; y++){
+			p = b;
+			for(x = w-1; x >= 0; x--){
+				*b++ = (bg->image[(y*w+x)/8] & (1<<(x&7))) ? 0 : 255;
+				for(i = 1; i < zoomX; i++, b++)
+					*b = b[-1];
+			}
+			for(i = 1; i < zoomY; i++, b += im->w)
+				memcpy(b, p, im->w);
+		}
+	}else if(bg->bitDepth == 4){
+		/* FIXME */
+		werrstr("bitDepth 4 not implemented");
+		free(im);
+		im = nil;
+	}else if(bg->bitDepth == 8){
+		/* FIXME */
+		werrstr("bitDepth 8 not implemented");
+		free(im);
+		im = nil;
+	}
+
+	return im;
+}
+
+GlyfImage *
+otfdrawglyf(Otf *o, Glyf *g, double ppem, double scaleX, double scaleY)
+{
+	if(g->type == GLYPH_SIMPLE || g->type == GLYPH_COMPONENT)
+		return outlineglyf(o, g, ppem, scaleX, scaleY);
+	if(g->type == GLYPH_BITMAP)
+		return bitmapglyf(o, g, ppem, scaleX, scaleY);
+	werrstr("empty glyph");
+	return nil;
 }
--- a/test.h
+++ b/test.h
@@ -19,6 +19,12 @@
 static int gind = -1, map, highlight = -1, runesonly;
 static double ppem, scaleX = 1, scaleY = 1;
 static Rune rune = NoRune;
+static char *gtypes[] = {
+	[GLYPH_EMPTY] = "empty",
+	[GLYPH_BITMAP] = "bitmap",
+	[GLYPH_SIMPLE] = "simple",
+	[GLYPH_COMPONENT] = "component",
+};
 
 static int
 dumpmap(Otfile *f, GlyfImage **im, int n)
@@ -151,12 +157,11 @@
 				werrstr("glyph %d: %r", gind);
 				return -1;
 			}
-			if(ppem > 0 && g->numberOfContours != 0){
+			if(ppem > 0 && g->type != GLYPH_EMPTY){
 				if((im[i] = otfdrawglyf(o, g, ppem, scaleX, scaleY)) == nil)
 					goto glypherr;
 			}else if(ppem <= 0){
-				out->print(out->aux, "%d (%s):\n", i,
-					g->simple ? "simple" : (g->component ? "component" : "empty"));
+				out->print(out->aux, "%d (%s):\n", i, gtypes[g->type]);
 				print_Glyf(out, indentΔ, o, g);
 			}
 			free(g);