shithub: fnt

Download patch

ref: bdc09059c1801bb7d6271b85b09040556a271de7
parent: 1d352256dfd09685a98184574d2fb7e046589ec8
author: Sigrid Solveig Haflínudóttir <sigrid@ftrv.se>
date: Mon Jul 15 17:39:26 EDT 2024

component glyph scaling; proper baseline; guide lines drawing; combine some of the test functionality

--- a/gen.rkt
+++ b/gen.rkt
@@ -605,8 +605,9 @@
 
 (out (at out-dir "otf.c")
      (λ ()
-       (printf (string-replace (port->string (open-input-file (at in-dir "otf.c.in")) #:close? #t)
+       (printf "~a"
+               (string-replace (port->string (open-input-file (at in-dir "otf.c.in")) #:close? #t)
                                "OTF_EXTRA_FIELDS\n"
                                (format extra-context-fields #:on-all remove-duplicates)))
        (printf "\n")
-       (printf (format (λ (c) (gen-c c #f #f))))))
+       (printf "~a" (format (λ (c) (gen-c c #f #f))))))
--- a/mkfile
+++ b/mkfile
@@ -22,7 +22,7 @@
 otfsys.$O: plan9/otf.c
 	$CC $CFLAGS plan9/otfsys.c
 
-test.$O: plan9/test.c
+test.$O: plan9/test.c test.h
 	$CC $CFLAGS plan9/test.c
 
 plan9/otf.h plan9/otf.c: gen.rkt otf.rkt otf.h.in otf.c.in
--- a/otf.c.in
+++ b/otf.c.in
@@ -257,6 +257,8 @@
 
 enum {
 	CGLYPH_FL_WORD_ARGS = 1<<0,
+	CGLYPH_FL_SCALE = 1<<3,
+	CGLYPH_FL_UNSCALED_COMPONENT_OFFSET = 1<<12,
 };
 
 static int
@@ -277,6 +279,8 @@
 	v->flags = b[0]<<8 | b[1];
 	v->glyphIndex = b[2]<<8 | b[3];
 	instr |= v->flags & CGLYPH_FL_INSTRUCTIONS;
+	if(v->flags & CGLYPH_FL_UNSCALED_COMPONENT_OFFSET)
+		v->flags &= ~(CGLYPH_FL_UNSCALED_COMPONENT_OFFSET | CGLYPH_FL_SCALED_COMPONENT_OFFSET);
 
 	if((v->flags & CGLYPH_FL_WORD_ARGS) != 0 && (b = otfreadn(o, 2*2)) != nil){
 		if(v->flags & CGLYPH_FL_SIGNED_XY){
@@ -298,8 +302,13 @@
 	if(b == nil)
 		goto err;
 	#define f2dot14(o) (f = b[2*o]<<8 | b[2*o+1], (f>>14)+(f&((1<<14)-1))/16384.0)
+	v->scaleX = 1.0;
+	v->scaleY = 1.0;
 	if((v->flags & CGLYPH_FL_SCALE) != 0 && (b = otfreadn(o, 2)) != nil){
-		v->scale = f2dot14(0);
+		v->scaleX = f2dot14(0);
+		v->scaleY = v->scaleX;
+		v->flags &= ~CGLYPH_FL_SCALE;
+		v->flags |= CGLYPH_FL_SCALE_XY;
 	}else if((v->flags & CGLYPH_FL_SCALE_XY) != 0 && (b = otfreadn(o, 2*2)) != nil){
 		v->scaleX = f2dot14(0);
 		v->scaleY = f2dot14(1);
@@ -340,7 +349,7 @@
 print_ComponentGlyph(Otfile *f, int indent, Otf *o, ComponentGlyph *v)
 {
 	void *a = f->aux;
-	f->print(a, "%*s%s: %#"PRIx16"%s%s%s%s%s%s%s%s%s%s%s\n", indent, "", "flags", v->flags,
+	f->print(a, "%*s%s: %#"PRIx16"%s%s%s%s%s%s%s%s%s%s\n", indent, "", "flags", v->flags,
 		(v->flags&CGLYPH_FL_SIGNED_XY)?" CGLYPH_FL_SIGNED_XY":"",
 		(v->flags&CGLYPH_FL_ROUND_TO_GRID_XY)?" CGLYPH_FL_ROUND_TO_GRID_XY":"",
 		(v->flags&CGLYPH_FL_SCALE)?" CGLYPH_FL_SCALE":"",
@@ -350,8 +359,7 @@
 		(v->flags&CGLYPH_FL_INSTRUCTIONS)?" CGLYPH_FL_INSTRUCTIONS":"",
 		(v->flags&CGLYPH_FL_METRICS)?" CGLYPH_FL_METRICS":"",
 		(v->flags&CGLYPH_FL_OVERLAP_COMPOUND)?" CGLYPH_FL_OVERLAP_COMPOUND":"",
-		(v->flags&CGLYPH_FL_SCALED_COMPONENT_OFFSET)?" CGLYPH_FL_SCALED_COMPONENT_OFFSET":"",
-		(v->flags&CGLYPH_FL_UNSCALED_COMPONENT_OFFSET)?" CGLYPH_FL_UNSCALED_COMPONENT_OFFSET":""
+		(v->flags&CGLYPH_FL_SCALED_COMPONENT_OFFSET)?" CGLYPH_FL_SCALED_COMPONENT_OFFSET":""
 	);
 	f->print(a, "%*s%s: %"PRIu16"\n", indent, "", "glyphIndex", v->glyphIndex);
 	if(v->arg1 != 0 || v->arg2 != 0){
@@ -358,9 +366,7 @@
 		f->print(f->aux, "%*s%s: %d\n", indent, "", "arg1", v->arg1);
 		f->print(f->aux, "%*s%s: %d\n", indent, "", "arg2", v->arg2);
 	}
-	if(v->flags & CGLYPH_FL_SCALE){
-		f->print(a, "%*s%s: %g\n", indent, "", "scale", v->scale);
-	}else if(v->flags & CGLYPH_FL_SCALE_XY){
+	if(v->flags & CGLYPH_FL_SCALE_XY){
 		f->print(a, "%*s%s: %g\n", indent, "", "scaleX", v->scaleX);
 		f->print(a, "%*s%s: %g\n", indent, "", "scaleY", v->scaleY);
 	}else if(v->flags & CGLYPH_FL_2X2_TRANSFORM){
--- a/otf.h.in
+++ b/otf.h.in
@@ -21,6 +21,7 @@
 	void *aux;
 	int (*seek)(void *aux, int off, int whence);
 	int (*read)(void *aux, void *dst, int sz);
+	int (*write)(void *aux, const void *src, int sz);
 	int (*print)(void *aux, const char *fmt, ...);
 };
 
@@ -29,7 +30,6 @@
 enum {
 	CGLYPH_FL_SIGNED_XY = 1<<1,
 	CGLYPH_FL_ROUND_TO_GRID_XY = 1<<2,
-	CGLYPH_FL_SCALE = 1<<3,
 	CGLYPH_FL_MORE_COMPONENTS = 1<<5,
 	CGLYPH_FL_SCALE_XY = 1<<6,
 	CGLYPH_FL_2X2_TRANSFORM = 1<<7,
@@ -37,7 +37,6 @@
 	CGLYPH_FL_METRICS = 1<<9,
 	CGLYPH_FL_OVERLAP_COMPOUND = 1<<10,
 	CGLYPH_FL_SCALED_COMPONENT_OFFSET = 1<<11,
-	CGLYPH_FL_UNSCALED_COMPONENT_OFFSET = 1<<12,
 };
 
 struct ComponentGlyph {
@@ -51,7 +50,7 @@
 		int arg2;
 		int dy;
 	};
-	float scale, scaleX, scale01, scale10, scaleY;
+	float scaleX, scale01, scale10, scaleY;
 	u16int numInstr;
 	u8int *instr;
 	ComponentGlyph *next;
@@ -97,5 +96,5 @@
 typedef struct Glyf Glyf;
 Glyf *otfglyf(Otf *o, int index);
 int otfglyfnum(Otf *o);
-int otfdrawglyf(Otf *o, Glyf *g, int ppem, GlyfImage *im);
+int otfdrawglyf(Otf *o, Glyf *g, int ppem, int gap, GlyfImage *im);
 int otfupem(Otf *o);
--- a/plan9/otf.c
+++ b/plan9/otf.c
@@ -271,6 +271,8 @@
 
 enum {
 	CGLYPH_FL_WORD_ARGS = 1<<0,
+	CGLYPH_FL_SCALE = 1<<3,
+	CGLYPH_FL_UNSCALED_COMPONENT_OFFSET = 1<<12,
 };
 
 static int
@@ -291,6 +293,8 @@
 	v->flags = b[0]<<8 | b[1];
 	v->glyphIndex = b[2]<<8 | b[3];
 	instr |= v->flags & CGLYPH_FL_INSTRUCTIONS;
+	if(v->flags & CGLYPH_FL_UNSCALED_COMPONENT_OFFSET)
+		v->flags &= ~(CGLYPH_FL_UNSCALED_COMPONENT_OFFSET | CGLYPH_FL_SCALED_COMPONENT_OFFSET);
 
 	if((v->flags & CGLYPH_FL_WORD_ARGS) != 0 && (b = otfreadn(o, 2*2)) != nil){
 		if(v->flags & CGLYPH_FL_SIGNED_XY){
@@ -312,8 +316,13 @@
 	if(b == nil)
 		goto err;
 	#define f2dot14(o) (f = b[2*o]<<8 | b[2*o+1], (f>>14)+(f&((1<<14)-1))/16384.0)
+	v->scaleX = 1.0;
+	v->scaleY = 1.0;
 	if((v->flags & CGLYPH_FL_SCALE) != 0 && (b = otfreadn(o, 2)) != nil){
-		v->scale = f2dot14(0);
+		v->scaleX = f2dot14(0);
+		v->scaleY = v->scaleX;
+		v->flags &= ~CGLYPH_FL_SCALE;
+		v->flags |= CGLYPH_FL_SCALE_XY;
 	}else if((v->flags & CGLYPH_FL_SCALE_XY) != 0 && (b = otfreadn(o, 2*2)) != nil){
 		v->scaleX = f2dot14(0);
 		v->scaleY = f2dot14(1);
@@ -354,7 +363,7 @@
 print_ComponentGlyph(Otfile *f, int indent, Otf *o, ComponentGlyph *v)
 {
 	void *a = f->aux;
-	f->print(a, "%*s%s: %#"PRIx16"%s%s%s%s%s%s%s%s%s%s%s\n", indent, "", "flags", v->flags,
+	f->print(a, "%*s%s: %#"PRIx16"%s%s%s%s%s%s%s%s%s%s\n", indent, "", "flags", v->flags,
 		(v->flags&CGLYPH_FL_SIGNED_XY)?" CGLYPH_FL_SIGNED_XY":"",
 		(v->flags&CGLYPH_FL_ROUND_TO_GRID_XY)?" CGLYPH_FL_ROUND_TO_GRID_XY":"",
 		(v->flags&CGLYPH_FL_SCALE)?" CGLYPH_FL_SCALE":"",
@@ -364,8 +373,7 @@
 		(v->flags&CGLYPH_FL_INSTRUCTIONS)?" CGLYPH_FL_INSTRUCTIONS":"",
 		(v->flags&CGLYPH_FL_METRICS)?" CGLYPH_FL_METRICS":"",
 		(v->flags&CGLYPH_FL_OVERLAP_COMPOUND)?" CGLYPH_FL_OVERLAP_COMPOUND":"",
-		(v->flags&CGLYPH_FL_SCALED_COMPONENT_OFFSET)?" CGLYPH_FL_SCALED_COMPONENT_OFFSET":"",
-		(v->flags&CGLYPH_FL_UNSCALED_COMPONENT_OFFSET)?" CGLYPH_FL_UNSCALED_COMPONENT_OFFSET":""
+		(v->flags&CGLYPH_FL_SCALED_COMPONENT_OFFSET)?" CGLYPH_FL_SCALED_COMPONENT_OFFSET":""
 	);
 	f->print(a, "%*s%s: %"PRIu16"\n", indent, "", "glyphIndex", v->glyphIndex);
 	if(v->arg1 != 0 || v->arg2 != 0){
@@ -372,9 +380,7 @@
 		f->print(f->aux, "%*s%s: %d\n", indent, "", "arg1", v->arg1);
 		f->print(f->aux, "%*s%s: %d\n", indent, "", "arg2", v->arg2);
 	}
-	if(v->flags & CGLYPH_FL_SCALE){
-		f->print(a, "%*s%s: %g\n", indent, "", "scale", v->scale);
-	}else if(v->flags & CGLYPH_FL_SCALE_XY){
+	if(v->flags & CGLYPH_FL_SCALE_XY){
 		f->print(a, "%*s%s: %g\n", indent, "", "scaleX", v->scaleX);
 		f->print(a, "%*s%s: %g\n", indent, "", "scaleY", v->scaleY);
 	}else if(v->flags & CGLYPH_FL_2X2_TRANSFORM){
--- a/plan9/otf.h
+++ b/plan9/otf.h
@@ -21,6 +21,7 @@
 	void *aux;
 	int (*seek)(void *aux, int off, int whence);
 	int (*read)(void *aux, void *dst, int sz);
+	int (*write)(void *aux, const void *src, int sz);
 	int (*print)(void *aux, const char *fmt, ...);
 };
 
@@ -29,7 +30,6 @@
 enum {
 	CGLYPH_FL_SIGNED_XY = 1<<1,
 	CGLYPH_FL_ROUND_TO_GRID_XY = 1<<2,
-	CGLYPH_FL_SCALE = 1<<3,
 	CGLYPH_FL_MORE_COMPONENTS = 1<<5,
 	CGLYPH_FL_SCALE_XY = 1<<6,
 	CGLYPH_FL_2X2_TRANSFORM = 1<<7,
@@ -37,7 +37,6 @@
 	CGLYPH_FL_METRICS = 1<<9,
 	CGLYPH_FL_OVERLAP_COMPOUND = 1<<10,
 	CGLYPH_FL_SCALED_COMPONENT_OFFSET = 1<<11,
-	CGLYPH_FL_UNSCALED_COMPONENT_OFFSET = 1<<12,
 };
 
 struct ComponentGlyph {
@@ -51,7 +50,7 @@
 		int arg2;
 		int dy;
 	};
-	float scale, scaleX, scale01, scale10, scaleY;
+	float scaleX, scale01, scale10, scaleY;
 	u16int numInstr;
 	u8int *instr;
 	ComponentGlyph *next;
@@ -97,7 +96,7 @@
 typedef struct Glyf Glyf;
 Glyf *otfglyf(Otf *o, int index);
 int otfglyfnum(Otf *o);
-int otfdrawglyf(Otf *o, Glyf *g, int ppem, GlyfImage *im);
+int otfdrawglyf(Otf *o, Glyf *g, int ppem, int gap, GlyfImage *im);
 int otfupem(Otf *o);
 
 typedef struct SubHeader SubHeader;
--- a/plan9/test.c
+++ b/plan9/test.c
@@ -2,48 +2,8 @@
 #include <libc.h>
 #include <bio.h>
 #include "otf.h"
+#include "test.h"
 
-static void
-dumpmap(Biobuf *out, GlyfImage *im, int n)
-{
-#define gap 1
-	int total, mid, npix, bw, bh, x, y, i, maxh, d;
-	u8int *b;
-
-	total = 0;
-	maxh = 0;
-	for(i = 0; i < n; i++){
-		if(maxh < im[i].h)
-			maxh = im[i].h;
-		total += im[i].w;
-	}
-	mid = total / n;
-	npix = (mid+gap)*(maxh+gap)*n;
-	bh = sqrt(npix);
-	bw = npix/bh;
-	bh *= 1.5;
-	npix *= 1.5;
-	npix += bw*gap;
-	if((b = malloc(npix)) == nil)
-		sysfatal("no memory");
-	memset(b, 0xff, npix);
-	x = y = gap;
-	for(i = 0; i < n; i++){
-		if((bw-x) < im[i].w+gap){
-			y += maxh + gap;
-			x = gap;
-		}
-		if((bh-y) < maxh+gap)
-			continue;
-		for(d = 0; d < im[i].h; d++)
-			memcpy(b + (maxh - im[i].h + y + im[i].baseline + d)*bw + x, im[i].b + d*im[i].w, im[i].w);
-		x += im[i].w + gap;
-	}
-	Bprint(out, "%11s %11d %11d %11d %11d ", "k8", 0, 0, bw, y+maxh*2+gap);
-	Bwrite(out, b, bw*(y+maxh*2+gap));
-	free(b);
-}
-
 static int
 otfseek(void *aux, int off, int whence)
 {
@@ -56,13 +16,16 @@
 	return Bread(aux, dst, sz);
 }
 
+static int
+otfwrite(void *aux, void *dst, int sz)
+{
+	return Bwrite(aux, dst, sz);
+}
+
 static void
-usage(void)
+usage(Otfile *f)
 {
-	fprint(2, "usage: %s [-g GLYPH_ID] [-G] [-i N] [-p PPM] font.otf ...\n", argv0);
-	fprint(2, " -g: specifies a single glyph id\n");
-	fprint(2, " -G: print out glyph ids, only ones that can be drawn atm (no compound yet)\n");
-	fprint(2, " -p: draw (of size in pixels per em) and write the image to stdout\n");
+	printusage(f);
 	exits("usage");
 }
 
@@ -69,32 +32,17 @@
 void
 main(int argc, char **argv)
 {
-	int i, gi, G, ppem;
+	int i;
 	Otfile in, out;
 	Otf *o;
 
-	gi = -1;
-	G = 0;
-	ppem = 0;
-	ARGBEGIN{
-	case 'g':
-		gi = strtol(EARGF(usage()), nil, 0);
-		break;
-	case 'G':
-		G++;
-		break;
-	case 'p':
-		ppem = strtol(EARGF(usage()), nil, 0);
-		break;
-	default:
-		usage();
-	}ARGEND
-
 	in.seek = otfseek;
 	in.read = otfread;
 	out.print = (void*)Bprint;
-
+	out.write = otfwrite;
 	out.aux = Bfdopen(1, OWRITE);
+	parseoptions();
+
 	for(i = 0; i < argc; i++){
 		if((in.aux = Bopen(argv[i], OREAD)) == nil || (o = otfopen(&in)) == nil){
 			fprint(2, "%r\n");
@@ -102,7 +50,7 @@
 		}
 		if(ppem <= 0)
 			Bprint(out.aux, "%s\n", argv[i]);
-		if(G && gi < 0){
+		if(map > 0 && gind < 0){
 			int i, n = otfglyfnum(o);
 			GlyfImage *im = ppem > 0 ? calloc(n, sizeof(*im)) : nil;
 			for(i = 0; i < n; i++){
@@ -110,7 +58,7 @@
 				if(g == nil)
 					sysfatal("glyf %d: %r", i);
 				if(ppem > 0 && g->numberOfContours != 0){
-					if(otfdrawglyf(o, g, ppem, &im[i]) != 0)
+					if(otfdrawglyf(o, g, ppem, gap, &im[i]) != 0)
 						sysfatal("glyf %d: %r", i);
 				}else if(ppem <= 0){
 					Bprint(out.aux, "%d (%s):\n", i,
@@ -120,7 +68,7 @@
 				free(g);
 			}
 			if(ppem > 0){
-				dumpmap(out.aux, im, n);
+				dumpmap(&out, im, n);
 				for(i = 0; i < n; i++)
 					free(im[i].b);
 				free(im);
@@ -127,24 +75,24 @@
 			}else{
 				fprint(2, "\n");
 			}
-		}else if(gi < 0){
+		}else if(gind < 0){
 			otfprint(o, &out, indentΔ);
 		}else{
 			int n = otfglyfnum(o);
-			if(gi >= n)
-				sysfatal("glyph %d out of range, max %d", gi, n-1);
-			Glyf *g = otfglyf(o, gi);
+			if(gind >= n)
+				sysfatal("glyph %d out of range, max %d", gind, n-1);
+			Glyf *g = otfglyf(o, gind);
 			if(g == nil){
-				fprint(2, "%d: %r\n", gi);
+				fprint(2, "%d: %r\n", gind);
 			}else if(ppem > 0){
 				GlyfImage im;
-				if(otfdrawglyf(o, g, ppem, &im) != 0)
+				if(otfdrawglyf(o, g, ppem, gap, &im) != 0)
 					sysfatal("%r");
 				fprint(1, "%11s %11d %11d %11d %11d ", "k8", 0, 0, im.w, im.h);
 				write(1, im.b, im.w*im.h);
 				free(im.b);
 			}else{
-				Bprint(out.aux, "\n%d:\n", gi);
+				Bprint(out.aux, "\n%d:\n", gind);
 				print_Glyf(&out, indentΔ, o, g);
 			}
 		}
--- a/rast.c
+++ b/rast.c
@@ -57,10 +57,11 @@
  */
 #define QBZR_PIX_SCALE 4.0
 
-#define GAP 0
+#define MAXCOMPONENTS 16
+#define ε 1.0e-6
+#define min(a,b) ((a)<(b)?(a):(b))
+#define max(a,b) ((a)>(b)?(a):(b))
 
-#define ε 1.0e-10
-
 static int
 closest²(int v)
 {
@@ -236,7 +237,7 @@
 	if(α₂ < 0){
 		α₂ = 0;
 		β₂ = (y₂-y₁)/(x₂-x₁)*(0-x₁)+y₁;
-	}else if(α₂ >= 1){
+	}else if(α₂ > 1){
 		α₂ = 1;
 		β₂ = (y₂-y₁)/(x₂-x₁)*(1-x₁)+y₁;
 	}
@@ -252,7 +253,7 @@
 	if(β₂ < 0){
 		β₂ = 0;
 		α₂ = (x₂-x₁)/(y₂-y₁)*(0-y₁)+x₁;
-	}else if(β₂ >= 1){
+	}else if(β₂ > 1){
 		β₂ = 1;
 		α₂ = (x₂-x₁)/(y₂-y₁)*(1-y₁)+x₁;
 	}
@@ -452,7 +453,7 @@
 }
 
 static SegQ *
-addpoints(SegQ *s, Spt *pts₀, int max, Glyf *g, int dx, int dy, Sval scale, Sval offY)
+addpoints(SegQ *s, Spt *pts₀, int max, Glyf *g)
 {
 	Point *p₀, *p, *prev;
 	Spt *e, *pts;
@@ -505,12 +506,12 @@
 		}
 
 		for(; pts <= e-3; pts += 2, s++){
-			s->v0[0] = (pts[0].x + dx) * scale;
-			s->v0[1] = (pts[0].y + dy) * scale + offY;
-			s->v1[0] = (pts[1].x + dx) * scale;
-			s->v1[1] = (pts[1].y + dy) * scale + offY;
-			s->v2[0] = (pts[2].x + dx) * scale;
-			s->v2[1] = (pts[2].y + dy) * scale + offY;
+			s->v0[0] = pts[0].x;
+			s->v0[1] = pts[0].y;
+			s->v1[0] = pts[1].x;
+			s->v1[1] = pts[1].y;
+			s->v2[0] = pts[2].x;
+			s->v2[1] = pts[2].y;
 		}
 	}
 	return s;
@@ -535,16 +536,14 @@
 	return npts < 2 ? 0 : npts;
 }
 
-#define MAXCOMPONENTS 16
-
 int
-otfdrawglyf(Otf *o, Glyf *g, int ppem, GlyfImage *im)
+otfdrawglyf(Otf *o, Glyf *g, int ppem, int gap, GlyfImage *im)
 {
 	int i, j, maxptstotal, maxpts, ngs, w, h, r, npx, baseline;
 	Glyf *gs[MAXCOMPONENTS];
 	ComponentGlyph *cg;
 	Sval scale, offY;
-	SegQ *s₀, *s;
+	SegQ *s₀, *s, *p;
 	Spt *pts;
 	Sval *fp;
 	u8int *b;
@@ -552,6 +551,7 @@
 	r = -1;
 	ngs = 0;
 	pts = nil;
+	fp = nil;
 
 	if(g->simple != nil){
 		gs[ngs++] = g;
@@ -568,42 +568,109 @@
 			maxpts = j;
 	}
 
-	scale = (Sval)ppem / otfupem(o);
-	baseline = g->yMin < 0 ? ceil(scale*-g->yMin) : 0;
-	offY = baseline > 0 ? (Sval)baseline - scale*-g->yMin : 0;
-	w = ceil((g->xMax - g->xMin)*scale);
-	h = ceil((g->yMax - g->yMin)*scale + (baseline ? 1 : 0));
-	npx = w*h;
+/*
+FIXME metrics (lsb+rsb)
+	for(cg = g->component, i = 0; cg != nil && i < ngs; cg = cg->next, i++){
+		if(cg->flags & CGLYPH_FL_METRICS){
+			...
+			break;
+		}
+	}
+*/
 
-	pts = calloc(1, maxpts*sizeof(*pts) + maxptstotal/2*sizeof(*s) + npx*sizeof(*fp));
-	s = s₀ = (SegQ*)(pts + maxptstotal);
-	fp = (Sval*)(s + maxpts/2);
+	double xMin = 99999, yMin = 99999, xMax = -99999, yMax = -99999;
+	scale = (Sval)ppem / otfupem(o);
+	pts = calloc(1, maxpts*sizeof(*pts) + maxptstotal/2*sizeof(*s));
+	p = s = s₀ = (SegQ*)(pts + maxpts);
 	cg = g->component;
 	for(i = 0; i < ngs; i++){
-		int dx = -g->xMin, dy = -g->yMin;
+		double dx = 0, dy = 0, gscaleX = scale, gscaleY = scale;
 		if(cg != nil){
 			if(cg->flags & CGLYPH_FL_SIGNED_XY){
-				dx += cg->dx;
-				dy += cg->dy;
+				dx = cg->dx;
+				dy = cg->dy;
+			}else{
+				dx = 0;
+				dy = 0;
 			}
+			if(cg->flags & CGLYPH_FL_SCALE_XY){
+				gscaleX *= cg->scaleX;
+				gscaleY *= cg->scaleY;
+			}
+			if((cg->flags & CGLYPH_FL_SCALED_COMPONENT_OFFSET) == 0){
+				dx *= gscaleX;
+				dy *= gscaleY;
+			}
+/* FIXME rounding
+			if(cg->flags & CGLYPH_FL_ROUND_TO_GRID_XY){
+				...
+			}
+*/
 			cg = cg->next;
 		}
-		if((s = addpoints(s, pts, maxpts, gs[i], dx, dy, scale, offY)) == nil)
+		if((s = addpoints(s, pts, maxpts, gs[i])) == nil)
 			goto done;
+		for(; p < s; p++){
+			p->p0.x = p->p0.x*gscaleX + dx;
+			xMin = min(xMin, p->p0.x);
+			xMax = max(xMax, p->p0.x);
+			p->p0.y = p->p0.y*gscaleY + dy;
+			yMin = min(yMin, p->p0.y);
+			yMax = max(yMax, p->p0.y);
+			p->p1.x = p->p1.x*gscaleX + dx;
+			xMin = min(xMin, p->p1.x);
+			xMax = max(xMax, p->p1.x);
+			p->p1.y = p->p1.y*gscaleY + dy;
+			yMin = min(yMin, p->p1.y);
+			yMax = max(yMax, p->p1.y);
+			p->p2.x = p->p2.x*gscaleX + dx;
+			xMin = min(xMin, p->p2.x);
+			xMax = max(xMax, p->p2.x);
+			p->p2.y = p->p2.y*gscaleY + dy;
+			yMin = min(yMin, p->p2.y);
+			yMax = max(yMax, p->p2.y);
+		}
 	}
 
-	if((r = qbzr(s₀, s-s₀, w, h, fp)) == 0){
-		b = (u8int*)pts;
+	if(s == s₀){
+		w = h = 1;
+		baseline = 0;
+	}else{
+		xMin -= gap;
+		yMin -= gap;
+		xMax += gap;
+		yMax += gap;
+
+		/* height+baseline is where it is in the image */
+		baseline = floor(yMin);
+		offY = -baseline;
+		yMax += offY;
+		w = ceil(xMax - xMin);
+		h = ceil(yMax);
+
+		for(p = s₀; p != s; p++){
+			p->p0.x -= xMin;
+			p->p1.x -= xMin;
+			p->p2.x -= xMin;
+			p->p0.y += offY;
+			p->p1.y += offY;
+			p->p2.y += offY;
+		}
+	}
+
+	npx = w*h;
+	fp = malloc(npx*sizeof(*fp));
+	if(qbzr(s₀, s-s₀, w, h, fp) == 0){
+		b = (u8int*)fp;
 		for(i = 0; i < npx; i++)
-			b[i] = 255 - (fp[i] <= 0 ? 0 : (fp[i] >= QBZR_PIX_SCALE ? 255 : fp[i]/QBZR_PIX_SCALE*255));
-		if((b = realloc(b, npx)) != nil){
+			b[i] = 255 - (fp[i] <= ε ? 0 : (fp[i] >= QBZR_PIX_SCALE ? 255 : fp[i]/QBZR_PIX_SCALE*255));
+		if((b = realloc(fp, npx)) != nil){
 			im->b = b;
 			im->w = w;
 			im->h = h;
 			im->baseline = baseline;
-			pts = nil;
-		}else{
-			r = -1;
+			fp = nil;
+			r = 0;
 		}
 	}
 
@@ -613,5 +680,6 @@
 			free(gs[i]);
 	}
 	free(pts);
+	free(fp);
 	return r;
 }
--- /dev/null
+++ b/test.h
@@ -1,0 +1,105 @@
+static void
+printusage(Otfile *f)
+{
+	f->print(f->aux, "usage: %s [-i GLYPH_ID] [-m] [-p PX] font.otf ...\n", argv0);
+	f->print(f->aux, " -i: specifies a single glyph id\n");
+	f->print(f->aux, " -g: gap (in pixels) to add to every glyph border\n");
+	f->print(f->aux, " -m: print out glyph ids or render them all (with -p)\n");
+	f->print(f->aux, " -p: draw (of size in pixels per em) and write the image to stdout\n");
+	f->print(f->aux, "Specifying -m more than once makes the program draw guide lines\n");
+}
+
+static int gap, gind = -1, map, ppem;
+
+static int
+dumpmap(Otfile *f, GlyfImage *im, int n)
+{
+	int x, y, i, j, t, maxh, prebase, postbase, d;
+	int gap, total, mid, npix, bw, bh, lines;
+	u8int *b;
+
+	total = 0;
+	maxh = 0;
+	gap = 1;
+	lines = map > 1;
+	for(i = 0; i < n; i++){
+		if(im[i].b == nil)
+			continue;
+		if(maxh < im[i].h)
+			maxh = im[i].h;
+		total += im[i].w;
+	}
+	mid = total / n;
+	npix = (mid+gap)*(maxh+gap)*n;
+	bh = sqrt(npix);
+	bw = npix/bh;
+	npix += bw*gap;
+	if((b = malloc(npix*3)) == nil)
+		return -1;
+	memset(b, 0xff, npix*3);
+	y = gap;
+	for(i = 0; i < n;){
+		for(prebase = postbase = x = 0, j = i; j < n; j++){
+			if(im[j].b == nil)
+				continue;
+			x += im[j].w + gap;
+			if(x > bw)
+				break;
+			if(prebase < im[j].h+im[j].baseline)
+				prebase = im[j].h+im[j].baseline;
+			if(postbase < im[j].baseline)
+				postbase = im[j].baseline;
+		}
+		maxh = prebase + postbase;
+		if(j == i || y+maxh > bh)
+			break;
+
+		for(x = 0; i < j; i++){
+			if(im[i].b == nil)
+				continue;
+			u8int *lt = b + ((y + prebase - (im[i].h+im[i].baseline))*bw + x)*3;
+			for(d = 0; d < im[i].h; d++, lt += bw*3){
+				for(t = 0; t < im[i].w; t++){
+					u8int r, g, b;
+					r = g = b = im[i].b[d*im[i].w + t];
+					if(lines && g == 0xff && (d == im[i].h+im[i].baseline)){
+						g = 0;
+						b = 0;
+					}else if(lines && g == 0xff && (t == 0 || t == im[i].w-1 || d == 0 || d == im[i].h-1)){
+						r = 0;
+						b = 0;
+					}
+					if(r != 0xff || g != 0xff || b != 0xff){ 
+						lt[t*3+0] = b;
+						lt[t*3+1] = g;
+						lt[t*3+2] = r;
+					}
+				}
+			}
+			x += im[i].w + gap;
+		}
+		y += maxh + gap;
+	}
+	f->print(f->aux, "%11s %11d %11d %11d %11d ", "r8g8b8", 0, 0, bw, y);
+	f->write(f->aux, b, bw*y*3);
+	free(b);
+	return 0;
+}
+
+#define parseoptions() \
+	ARGBEGIN{ \
+	case 'g': \
+		gap = strtol(EARGF(usage(&out)), nil, 0); \
+		break; \
+	case 'i': \
+		gind = strtol(EARGF(usage(&out)), nil, 0); \
+		break; \
+	case 'm': \
+		map++; \
+		break; \
+	case 'p': \
+		ppem = strtol(EARGF(usage(&out)), nil, 0); \
+		break; \
+	default: \
+		usage(&out); \
+	}ARGEND
--- a/unix/test.c
+++ b/unix/test.c
@@ -1,3 +1,4 @@
+#include <assert.h>
 #include <err.h>
 #include <math.h>
 #include <stdio.h>
@@ -5,47 +6,8 @@
 #include <string.h>
 #include "otf.h"
 
-static void
-dumpmap(FILE *out, GlyfImage *im, int n)
-{
-#define gap 1
-	int total, mid, npix, bw, bh, x, y, i, maxh, d;
-	u8int *b;
+#define nil NULL
 
-	total = 0;
-	maxh = 0;
-	for(i = 0; i < n; i++){
-		if(maxh < im[i].h)
-			maxh = im[i].h;
-		total += im[i].w;
-	}
-	mid = total / n;
-	npix = (mid+gap)*(maxh+gap)*n;
-	bh = sqrt(npix);
-	bw = npix/bh;
-	bh *= 1.5;
-	npix *= 1.5;
-	npix += bw*gap;
-	if((b = malloc(npix)) == NULL)
-		err(1, NULL);
-	memset(b, 0xff, npix);
-	x = y = gap;
-	for(i = 0; i < n; i++){
-		if((bw-x) < im[i].w+gap){
-			y += maxh + gap;
-			x = gap;
-		}
-		if((bh-y) < maxh+gap)
-			continue;
-		for(d = 0; d < im[i].h; d++)
-			memcpy(b + (maxh - im[i].h + y + im[i].baseline + d)*bw + x, im[i].b + d*im[i].w, im[i].w);
-		x += im[i].w + gap;
-	}
-	fprintf(out, "%11s %11d %11d %11d %11d ", "k8", 0, 0, bw, y+maxh+gap);
-	fwrite(b, 1, bw*(y+maxh+gap), out);
-	free(b);
-}
-
 static char *argv0;
 
 #define	ARGBEGIN	for((argv0? 0: (argv0=*argv)),argv++,argc--;\
@@ -68,6 +30,8 @@
 #define	EARGF(x)		(_argt=_args, _args="",\
 				(*_argt? _argt: argv[1]? (argc--, *++argv): (x, (char*)0)))
 
+#include "test.h"
+
 static int
 otfseek(void *aux, int off, int whence)
 {
@@ -82,13 +46,16 @@
 	return fread(dst, 1, sz, aux);
 }
 
+static int
+otfwrite(void *aux, const void *src, int sz)
+{
+	return fwrite(src, 1, sz, aux);
+}
+
 static void
-usage(void)
+usage(Otfile *out)
 {
-	fprintf(stderr, "usage: %s [-g GLYPH_ID] [-G] [-i N] [-s PX] font.otf ...\n", argv0);
-	fprintf(stderr, " -g: specifies a single glyph id\n");
-	fprintf(stderr, " -G: print out glyph ids, only ones that can be drawn atm (no compound yet)\n");
-	fprintf(stderr, " -p: draw (of size in pixels per em) and write the image to stdout\n");
+	printusage(out);
 	exit(1);
 }
 
@@ -95,32 +62,17 @@
 int
 main(int argc, char **argv)
 {
-	int i, gi, G, ppem;
 	Otfile in, out;
+	int i;
 	Otf *o;
 
-	gi = -1;
-	G = 0;
-	ppem = 0;
-	ARGBEGIN{
-	case 'g':
-		gi = strtol(EARGF(usage()), NULL, 0);
-		break;
-	case 'G':
-		G++;
-		break;
-	case 'p':
-		ppem = strtol(EARGF(usage()), NULL, 0);
-		break;
-	default:
-		usage();
-	}ARGEND
-
+	out.print = (void*)fprintf;
+	out.write = otfwrite;
+	out.aux = fdopen(1, "wb");
 	in.seek = otfseek;
 	in.read = otfread;
-	out.print = (void*)fprintf;
+	parseoptions();
 
-	out.aux = fdopen(1, "wb");
 	for(i = 0; i < argc; i++){
 		if((in.aux = fopen(argv[i], "rb")) == NULL)
 			err(1, "%s", argv[i]);
@@ -127,8 +79,8 @@
 		if((o = otfopen(&in)) == NULL)
 			errx(1, "%s: %s", argv[i], otferrstr());
 		if(ppem <= 0)
-			fprintf(out.aux, "%s\n", argv[i]);
-		if(G && gi < 0){
+			out.print(out.aux, "%s\n", argv[i]);
+		if(map && gind < 0){
 			int i, n = otfglyfnum(o);
 			GlyfImage *im = ppem > 0 ? calloc(n, sizeof(*im)) : NULL;
 			for(i = 0; i < n; i++){
@@ -136,10 +88,10 @@
 				if(g == NULL)
 					errx(1, "glyph %d: %s", i, otferrstr());
 				if(ppem > 0 && g->numberOfContours != 0){
-					if(otfdrawglyf(o, g, ppem, im+i) != 0)
+					if(otfdrawglyf(o, g, ppem, gap, im+i) != 0)
 						errx(1, "glyph %d: %s", i, otferrstr());
 				}else if(ppem <= 0){
-					fprintf(out.aux, "%d (%s):\n", i,
+					out.print(out.aux, "%d (%s):\n", i,
 						g->simple ? "simple" : (g->component ? "component" : "empty"));
 					print_Glyf(&out, indentΔ, o, g);
 				}
@@ -146,33 +98,30 @@
 				free(g);
 			}
 			if(ppem > 0){
-				dumpmap(out.aux, im, n);
+				if(dumpmap(&out, im, n) != 0)
+					err(1, nil);
 				for(i = 0; i < n; i++)
 					free(im[i].b);
 				free(im);
 			}
-		}else if(gi < 0){
+		}else if(gind < 0){
 			otfprint(o, &out, indentΔ);
 		}else{
 			int n = otfglyfnum(o);
-			if(gi >= n)
-				errx(1, "glyph %d out of range, max %d", gi, n-1);
-			Glyf *g = otfglyf(o, gi);
+			if(gind >= n)
+				errx(1, "glyph %d out of range, max %d", gind, n-1);
+			Glyf *g = otfglyf(o, gind);
 			if(g == NULL){
-				errx(1, "glyph %d: %s", gi, otferrstr());
+				errx(1, "glyph %d: %s", gind, otferrstr());
 			}else if(ppem > 0){
-				if(g->component != NULL)
-					errx(1, "glyph %d: component", gi);
-				else{
-					GlyfImage im;
-					if(otfdrawglyf(o, g, ppem, &im) != 0)
-						errx(1, "glyph %d: %s", gi, otferrstr());
-					fprintf(out.aux, "%11s %11d %11d %11d %11d ", "k8", 0, 0, im.w, im.h);
-					fwrite(im.b, 1, im.w*im.h, out.aux);
-					free(im.b);
-				}
+				GlyfImage im;
+				if(otfdrawglyf(o, g, ppem, gap, &im) != 0)
+					errx(1, "glyph %d: %s", gind, otferrstr());
+				out.print(out.aux, "%11s %11d %11d %11d %11d ", "k8", 0, 0, im.w, im.h);
+				out.write(out.aux, im.b, im.w*im.h);
+				free(im.b);
 			}else{
-				fprintf(out.aux, "\n%d:\n", gi);
+				out.print(out.aux, "\n%d:\n", gind);
 				print_Glyf(&out, indentΔ, o, g);
 			}
 		}