shithub: fnt

Download patch

ref: acc2272a84dfe957c57c26768144b7a9ddd5da27
parent: 542ec0666f609eb98baf5187bdf1d84b2b08e71c
author: Sigrid Solveig Haflínudóttir <sigrid@ftrv.se>
date: Sun Jun 23 17:28:17 EDT 2024

add string parsing; move most manually-written C code to otfpriv.h

--- a/gen.rkt
+++ b/gen.rkt
@@ -64,6 +64,9 @@
    (define (c-type t)
      (type-c t))])
 
+(define (type-string? t)
+  (and (type? t) (equal? (type-c t) 'char)))
+
 (define/contract (type-size t)
   (-> type? positive?)
   (/ (type-bits t) 8))
@@ -118,6 +121,7 @@
      (define (format-number x)
        (if (<= x 32768) (~r x) (~r x #:base 16 #:sign '("0x" "" "-0x"))))
      (define (parse-if-error read)
+       (define t (field-type f))
        (define is-ptr (and (cmplx? (field-type f)) (field-offset f)))
        (match (field-count f)
          [#f
@@ -136,7 +140,15 @@
                    (~a "if(read_" (name (field-type f)) "(o, " (if is-ptr "" "&") ref ") < 0){")))
               (if index empty (~a "if(otfreadn(o, " (size (field-type f)) ") == nil){")))]
          [count
-          #:when (type? (field-type f))
+          #:when (type-string? t)
+          (list (~a "if((b = otfreadn(o, " count ")) == nil)")
+                (~a "\tgoto err;")
+                (if (equal? t String-UTF16)
+                    (list (~a ref " = malloc(" count "/2+1);")
+                          (~a "utf16to8((u8int*)" ref ", " count "/2+1, b, " count ");"))
+                    (~a ref " = strtoutf8(o, b, " count ");")))]
+         [count
+          #:when (type? t)
           (if (field-unused? f)
               empty
               (list (if (number? count) empty (~a ref " = malloc(" count "*sizeof(*" ref "));"))
@@ -167,8 +179,8 @@
        (define at (field-offset f))
        (if (not at)
            lst
-           (list (~a "if(v->" at " != 0){")
-                 (~a "\tif(otfpushrange(o, v->" at ", -1) < 0)")
+           (list (~a "if(" (fmt-expr at) " != 0){")
+                 (~a "\tif(otfpushrange(o, " (fmt-expr at) ", -1) < 0)")
                  (~a "\t\tgoto err;")
                  (indent lst)
                  (~a "\tif(otfpoprange(o) < 0)")
@@ -224,7 +236,7 @@
 
 (define (field-print-c f)
   (define t (field-type f))
-  (define cnt (field-count f))
+  (define cnt (and (not (type-string? t)) (field-count f)))
   (define basic-array (and cnt (type? t)))
   (define fixed-array (and basic-array (number? cnt)))
   (define array-index (if cnt "[i]" ""))
@@ -307,6 +319,7 @@
          (or (empty? g)
              (and (type? (field-type f))
                   (type? (field-type (car g)))
+                  (not (or (field-offset f) (field-offset (car g))))
                   (no-vla? f)
                   (no-vla? (car g))
                   (equal? (field-cond f) (field-cond (car g))))))
@@ -336,15 +349,18 @@
        (p g 0))
      (define (gen-group-c fields)
        (define unused (andmap field-unused? fields))
-       (wrap-cond-c (field-cond (car fields))
-                    (if (cmplx? (field-type (car fields)))
-                        (map (λ (f) (super-gen-c f #f #f)) fields)
-                        (let* ([sum (foldr (λ (f accu) (add (field-size f) accu)) 0 fields)]
-                               [lst (flatten (list* (~a "if((b = otfreadn(o, " sum ")) == nil)")
-                                                    (~a "\tgoto err;")
-                                                    (if unused "USED(b);" empty)
-                                                    (parse-group fields)))])
-                          lst))))
+       (wrap-cond-c
+        (field-cond (car fields))
+        (if (cmplx? (field-type (car fields)))
+            (map (λ (f) (super-gen-c f #f #f)) fields)
+            (let* ([sum (foldr (λ (f accu) (add (field-size f) accu)) 0 fields)]
+                   [lst (flatten (list (if (field-offset (car fields))
+                                           empty
+                                           (list (~a "if((b = otfreadn(o, " sum ")) == nil)")
+                                                 (~a "\tgoto err;")
+                                                 (if unused "USED(b);" empty)))
+                                       (parse-group fields)))])
+              lst))))
      (flatten
       (append
        (list (~a "")
@@ -445,6 +461,8 @@
     [(_ _ _ ({~literal at} ref:ref))
      #:fail-when (not (type-offset? (syntax-e #'ref.type))) "can't be used as an offset"
      #''(at ref)]
+    ; FIXME - check fields and ops/numbers
+    [(_ _ _ ({~literal at} e:expr)) #''(at e)]
     [(_ type _ {~literal hex})
      #:fail-when (not (type-number? (syntax-e #'type))) "not a number type"
      #''(verb "%#ux")]
@@ -456,7 +474,7 @@
           p
           ref
           vs ...)]
-    [(_ _ _ (p:compop oref:oref e:expr))
+    [(_ _ _ (p:compop oref:oref e:expr)) ; FIXME - check fields and ops/numbers
      #''(cond
           p
           oref
@@ -561,191 +579,8 @@
        (printf #<<EOF
 };
 
-struct Range {
-	int start;
-	int len;
-	int prevoff;
-	Range *par;
-};
+#include "otfpriv.h"
 
-Otf *
-otfopen(char *path)
-{
-	Otf *o;
-	Biobuf *f;
-
-	if((f = Bopen(path, OREAD)) == nil)
-		return nil;
-	if((o = calloc(1, sizeof(*o))) == nil){
-		werrstr("no memory");
-		Bterm(f);
-	}else{
-		o->f = f;
-	}
-	return o;
-}
-
-void
-otfclose(Otf *o)
-{
-	if(o == nil)
-		return;
-	// FIXME traverse and free everything
-	free(o);
-}
-
-static int
-otfpushrange(Otf *o, int off, int len)
-{
-	Range *r;
-	int x;
-
-	r = nil;
-	if(o->r != nil){
-		if(len < 0)
-			len = o->r->len - off;
-		if(len < 0 || off+len > o->r->len){
-			werrstr("range overflow (len %d) by %d bytes", len, off+len - o->r->len);
-			goto err;
-		}
-		off += o->r->start;
-	}else if(len < 0){
-		len = 0x7fffffff;
-	}
-	if((r = malloc(sizeof(*r))) == nil){
-		werrstr("no memory");
-		goto err;
-	}
-	r->par = o->r;
-	r->start = off;
-	r->len = len;
-	r->prevoff = o->off;
-	if((x = Bseek(o->f, off, 0)) != off){
-		werrstr("seek offset: need %d, got %d", off, x);
-		goto err;
-	}
-	o->off = off;
-	o->r = r;
-	return 0;
-err:
-	free(r);
-	return -1;
-}
-
-static int
-otfpoprange(Otf *o)
-{
-	Range *r;
-	int x;
-
-	r = o->r;
-	if(r == nil){
-		werrstr("pop without push");
-		goto err;
-	}
-	if((x = Bseek(o->f, r->prevoff, 0)) != r->prevoff){
-		werrstr("seek offset: need %d, got %d", r->prevoff, x);
-		goto err;
-	}
-	o->off = r->prevoff;
-	o->r = r->par;
-	free(r);
-	return 0;
-err:
-	return -1;
-}
-
-static u8int *
-otfreadn(Otf *o, int n)
-{
-	Range *r;
-	u8int *b;
-	int x;
-
-	r = o->r;
-	if(r != nil && o->off+n > r->start+r->len){
-		werrstr("need %d at %d, have %d at %d", n, o->off, r->len, r->start);
-		goto err;
-	}
-	if(n > o->bufsz){
-		if((b = realloc(o->buf, n)) == nil){
-			werrstr("no memory");
-			goto err;
-		}
-		o->buf = b;
-		o->bufsz = n;
-	}
-	if((x = Bread(o->f, o->buf, n)) != n){
-		werrstr("need %d, got %d; off %d", n, x, o->off);
-		goto err;
-	}
-	o->off += n;
-
-	return o->buf;
-err:
-	return nil;
-}
-
-static int
-otfarray(Otf *o, void **arr_, void *fun_, int elsz, int num)
-{
-	int i;
-	int (*fun)(Otf*, void*);
-	u8int *arr;
-
-	if((arr = calloc(num, elsz)) == nil){
-		werrstr("no memory");
-		goto err;
-	}
-	fun = fun_;
-	for(i = 0; i < num; i++){
-		if(fun(o, arr + i*elsz) < 0)
-			goto err;
-	}
-	*arr_ = arr;
-	return 0;
-err:
-	free(arr);
-	return -1;
-}
-
 EOF
                )
-       (printf (format (λ (c) (gen-c c #f #f))))
-       (printf #<<EOF
-
-int indentΔ = 2;
-
-static int
-Tfmt(Fmt *f)
-{
-	Tm t;
-	s64int v = va_arg(f->args, s64int);
-	return fmtprint(f, "%τ", tmfmt(tmtime(&t, v, nil), nil));
-}
-
-static int
-Vfmt(Fmt *f)
-{
-	u32int v = va_arg(f->args, u32int);
-	return fmtprint(f, "%d.%d", v>>16, v&0xffff);
-}
-
-static int
-tfmt(Fmt *f)
-{
-	u32int v = va_arg(f->args, u32int);
-	return fmtprint(f, "%c%c%c%c", v>>24, v>>16, v>>8, v>>0);
-}
-
-void
-otfinit(void)
-{
-	tmfmtinstall();
-	fmtinstall('V', Vfmt);
-	fmtinstall('T', Tfmt);
-	fmtinstall('t', tfmt);
-}
-
-EOF
-               )))
+       (printf (format (λ (c) (gen-c c #f #f))))))
--- a/mkfile
+++ b/mkfile
@@ -9,6 +9,7 @@
 
 HFILES=\
   otf.h\
+  otfpriv.h\
 
 default:V: all
 
--- a/otf.c
+++ b/otf.c
@@ -16,6 +16,9 @@
 	s16int indexToLocFormat;
 	u16int numberOfHMetrics;
 	u16int numGlyphs;
+	u16int platformID;
+	u16int encodingID;
+	u16int storageOffset;
 	u16int firstGlyphIndex;
 	u16int lastGlyphIndex;
 	u16int axisCount;
@@ -26,154 +29,8 @@
 	u16int axisValueCount;
 };
 
-struct Range {
-	int start;
-	int len;
-	int prevoff;
-	Range *par;
-};
+#include "otfpriv.h"
 
-Otf *
-otfopen(char *path)
-{
-	Otf *o;
-	Biobuf *f;
-
-	if((f = Bopen(path, OREAD)) == nil)
-		return nil;
-	if((o = calloc(1, sizeof(*o))) == nil){
-		werrstr("no memory");
-		Bterm(f);
-	}else{
-		o->f = f;
-	}
-	return o;
-}
-
-void
-otfclose(Otf *o)
-{
-	if(o == nil)
-		return;
-	// FIXME traverse and free everything
-	free(o);
-}
-
-static int
-otfpushrange(Otf *o, int off, int len)
-{
-	Range *r;
-	int x;
-
-	r = nil;
-	if(o->r != nil){
-		if(len < 0)
-			len = o->r->len - off;
-		if(len < 0 || off+len > o->r->len){
-			werrstr("range overflow (len %d) by %d bytes", len, off+len - o->r->len);
-			goto err;
-		}
-		off += o->r->start;
-	}else if(len < 0){
-		len = 0x7fffffff;
-	}
-	if((r = malloc(sizeof(*r))) == nil){
-		werrstr("no memory");
-		goto err;
-	}
-	r->par = o->r;
-	r->start = off;
-	r->len = len;
-	r->prevoff = o->off;
-	if((x = Bseek(o->f, off, 0)) != off){
-		werrstr("seek offset: need %d, got %d", off, x);
-		goto err;
-	}
-	o->off = off;
-	o->r = r;
-	return 0;
-err:
-	free(r);
-	return -1;
-}
-
-static int
-otfpoprange(Otf *o)
-{
-	Range *r;
-	int x;
-
-	r = o->r;
-	if(r == nil){
-		werrstr("pop without push");
-		goto err;
-	}
-	if((x = Bseek(o->f, r->prevoff, 0)) != r->prevoff){
-		werrstr("seek offset: need %d, got %d", r->prevoff, x);
-		goto err;
-	}
-	o->off = r->prevoff;
-	o->r = r->par;
-	free(r);
-	return 0;
-err:
-	return -1;
-}
-
-static u8int *
-otfreadn(Otf *o, int n)
-{
-	Range *r;
-	u8int *b;
-	int x;
-
-	r = o->r;
-	if(r != nil && o->off+n > r->start+r->len){
-		werrstr("need %d at %d, have %d at %d", n, o->off, r->len, r->start);
-		goto err;
-	}
-	if(n > o->bufsz){
-		if((b = realloc(o->buf, n)) == nil){
-			werrstr("no memory");
-			goto err;
-		}
-		o->buf = b;
-		o->bufsz = n;
-	}
-	if((x = Bread(o->f, o->buf, n)) != n){
-		werrstr("need %d, got %d; off %d", n, x, o->off);
-		goto err;
-	}
-	o->off += n;
-
-	return o->buf;
-err:
-	return nil;
-}
-
-static int
-otfarray(Otf *o, void **arr_, void *fun_, int elsz, int num)
-{
-	int i;
-	int (*fun)(Otf*, void*);
-	u8int *arr;
-
-	if((arr = calloc(num, elsz)) == nil){
-		werrstr("no memory");
-		goto err;
-	}
-	fun = fun_;
-	for(i = 0; i < num; i++){
-		if(fun(o, arr + i*elsz) < 0)
-			goto err;
-	}
-	*arr_ = arr;
-	return 0;
-err:
-	free(arr);
-	return -1;
-}
-
 int
 read_SubHeader(Otf *o, SubHeader *v)
 {
@@ -1079,11 +936,22 @@
 	if((b = otfreadn(o, 12)) == nil)
 		goto err;
 	v->platformID = b[0]<<8 | b[1];
+	o->platformID = v->platformID;
 	v->encodingID = b[2]<<8 | b[3];
+	o->encodingID = v->encodingID;
 	v->languageID = b[4]<<8 | b[5];
 	v->nameID = b[6]<<8 | b[7];
-	v->length = b[8]<<8 | b[9];
+	v->stringLength = b[8]<<8 | b[9];
 	v->stringOffset = b[10]<<8 | b[11];
+	if((o->storageOffset+v->stringOffset) != 0){
+		if(otfpushrange(o, (o->storageOffset+v->stringOffset), -1) < 0)
+			goto err;
+		if((b = otfreadn(o, v->stringLength)) == nil)
+			goto err;
+		v->string = strtoutf8(o, b, v->stringLength);
+		if(otfpoprange(o) < 0)
+			goto err;
+	}
 	return 0;
 err:
 	werrstr("%s: %r", "NameRecord");
@@ -1097,8 +965,9 @@
 	Bprint(f, "%*s%s: %ud\n", indent, "", "encodingID", v->encodingID);
 	Bprint(f, "%*s%s: %ud\n", indent, "", "languageID", v->languageID);
 	Bprint(f, "%*s%s: %ud\n", indent, "", "nameID", v->nameID);
-	Bprint(f, "%*s%s: %ud\n", indent, "", "length", v->length);
+	Bprint(f, "%*s%s: %ud\n", indent, "", "stringLength", v->stringLength);
 	Bprint(f, "%*s%s: %ud\n", indent, "", "stringOffset", v->stringOffset);
+	Bprint(f, "%*s%s: %s\n", indent, "", "string", v->string);
 	USED(o);
 }
 
@@ -1110,6 +979,16 @@
 		goto err;
 	v->length = b[0]<<8 | b[1];
 	v->langTagOffset = b[2]<<8 | b[3];
+	if((o->storageOffset+v->langTagOffset) != 0){
+		if(otfpushrange(o, (o->storageOffset+v->langTagOffset), -1) < 0)
+			goto err;
+		if((b = otfreadn(o, v->length)) == nil)
+			goto err;
+		v->langTag = malloc(v->length/2+1);
+		utf16to8((u8int*)v->langTag, v->length/2+1, b, v->length);
+		if(otfpoprange(o) < 0)
+			goto err;
+	}
 	return 0;
 err:
 	werrstr("%s: %r", "LangTagRecord");
@@ -1121,6 +1000,7 @@
 {
 	Bprint(f, "%*s%s: %ud\n", indent, "", "length", v->length);
 	Bprint(f, "%*s%s: %ud\n", indent, "", "langTagOffset", v->langTagOffset);
+	Bprint(f, "%*s%s: %s\n", indent, "", "langTag", v->langTag);
 	USED(o);
 }
 
@@ -1137,6 +1017,7 @@
 	}
 	v->count = b[2]<<8 | b[3];
 	v->storageOffset = b[4]<<8 | b[5];
+	o->storageOffset = v->storageOffset;
 	if(otfarray(o, &v->nameRecord, read_NameRecord, sizeof(NameRecord), v->count) < 0){
 		werrstr("%s: %r", "nameRecord");
 		goto err;
@@ -5017,37 +4898,4 @@
 		print_TableRecord(f, indent+indentΔ, o, &v->tableRecords[i]);
 	}
 	USED(o);
-}
-
-int indentΔ = 2;
-
-static int
-Tfmt(Fmt *f)
-{
-	Tm t;
-	s64int v = va_arg(f->args, s64int);
-	return fmtprint(f, "%τ", tmfmt(tmtime(&t, v, nil), nil));
-}
-
-static int
-Vfmt(Fmt *f)
-{
-	u32int v = va_arg(f->args, u32int);
-	return fmtprint(f, "%d.%d", v>>16, v&0xffff);
-}
-
-static int
-tfmt(Fmt *f)
-{
-	u32int v = va_arg(f->args, u32int);
-	return fmtprint(f, "%c%c%c%c", v>>24, v>>16, v>>8, v>>0);
-}
-
-void
-otfinit(void)
-{
-	tmfmtinstall();
-	fmtinstall('V', Vfmt);
-	fmtinstall('T', Tfmt);
-	fmtinstall('t', tfmt);
 }
--- a/otf.h
+++ b/otf.h
@@ -382,8 +382,9 @@
 	u16int encodingID;
 	u16int languageID;
 	u16int nameID;
-	u16int length;
+	u16int stringLength;
 	u16int stringOffset;
+	char *string;
 };
 
 int read_NameRecord(Otf *o, NameRecord *v);
@@ -392,6 +393,7 @@
 struct LangTagRecord {
 	u16int length;
 	u16int langTagOffset;
+	char *langTag;
 };
 
 int read_LangTagRecord(Otf *o, LangTagRecord *v);
--- a/otf.rkt
+++ b/otf.rkt
@@ -13,6 +13,8 @@
   (member type
           '(uint8 int8 uint16 int16 uint24 uint32 int32 Version16Dot16 Offset16 Offset24 Offset32)))
 
+(mktype String 8 char "%s")
+(mktype String-UTF16 8 char "%s")
 (mktype uint8 8 u8int "%ud")
 (mktype int8 8 s8int "%d")
 (mktype uint16 16 u16int "%ud")
@@ -212,19 +214,23 @@
          #:tag "post")
 
 (mkcmplx NameRecord
-         {uint16 platformID}
-         {uint16 encodingID}
+         {uint16 platformID ->o}
+         {uint16 encodingID ->o}
          {uint16 languageID}
          {uint16 nameID}
+         {uint16 stringLength}
+         {Offset16 stringOffset}
+         {String string [stringLength] (at (+ o->storageOffset stringOffset))})
+
+(mkcmplx LangTagRecord
          {uint16 length}
-         {Offset16 stringOffset})
+         {Offset16 langTagOffset}
+         {String-UTF16 langTag [length] (at (+ o->storageOffset langTagOffset))})
 
-(mkcmplx LangTagRecord {uint16 length} {Offset16 langTagOffset})
-
 (mkcmplx TableName
          {uint16 version (== 0 1)}
          {uint16 count}
-         {Offset16 storageOffset}
+         {Offset16 storageOffset ->o}
          {NameRecord nameRecord [count]}
          {uint16 langTagCount (>= version 1)}
          {LangTagRecord langTagRecord [langTagCount] (>= version 1)}
--- /dev/null
+++ b/otfpriv.h
@@ -1,0 +1,255 @@
+struct Range {
+	int start;
+	int len;
+	int prevoff;
+	Range *par;
+};
+
+#define rchr(s) (be ? ((s)[0]<<8 | (s)[1]) : ((s)[1]<<8 | (s)[0]))
+
+static u8int mark[] = {0x00, 0x00, 0xc0, 0xe0, 0xf0};
+
+static int
+utf16to8(u8int *o, int osz, u8int *s, int sz)
+{
+	u32int c, c2;
+	int i, wr, j, be;
+
+	i = 0;
+	be = 1;
+	if(s[0] == 0xfe && s[1] == 0xff)
+		i += 2;
+	else if(s[0] == 0xff && s[1] == 0xfe){
+		be = 0;
+		i += 2;
+	}
+
+	for(; i < sz-1 && osz > 1;){
+		c = rchr(&s[i]);
+		i += 2;
+		if(c >= 0xd800 && c <= 0xdbff && i < sz-1){
+			c2 = rchr(&s[i]);
+			if(c2 >= 0xdc00 && c2 <= 0xdfff){
+				c = 0x10000 | (c - 0xd800)<<10 | (c2 - 0xdc00);
+				i += 2;
+			}else
+				return -1;
+		}else if(c >= 0xdc00 && c <= 0xdfff)
+			return -1;
+
+		if(c < 0x80)
+			wr = 1;
+		else if(c < 0x800)
+			wr = 2;
+		else if(c < 0x10000)
+			wr = 3;
+		else
+			wr = 4;
+
+		osz -= wr;
+		if(osz < 1)
+			break;
+
+		o += wr;
+		for(j = wr; j > 1; j--){
+			*(--o) = (c & 0xbf) | 0x80;
+			c >>= 6;
+		}
+		*(--o) = (u8int)c | mark[wr];
+		o += wr;
+	}
+
+	*o = 0;
+	return i;
+}
+
+static char *
+strtoutf8(Otf *o, u8int *in, int sz)
+{
+	char *s;
+
+	// FIXME this is not sufficient, obviously - need more platform/encoding handling
+	if(o->platformID == 0 || (o->platformID == 3 && o->encodingID == 1)){
+		s = malloc(sz/2+1);
+		utf16to8((u8int*)s, sz/2+1, in, sz);
+	}else{
+		s = malloc(sz+1);
+		memcpy(s, in, sz);
+		s[sz] = 0;
+	}
+	return s;
+}
+
+Otf *
+otfopen(char *path)
+{
+	Otf *o;
+	Biobuf *f;
+
+	if((f = Bopen(path, OREAD)) == nil)
+		return nil;
+	if((o = calloc(1, sizeof(*o))) == nil){
+		werrstr("no memory");
+		Bterm(f);
+	}else{
+		o->f = f;
+	}
+	return o;
+}
+
+void
+otfclose(Otf *o)
+{
+	if(o == nil)
+		return;
+	// FIXME traverse and free everything
+	free(o);
+}
+
+static int
+otfpushrange(Otf *o, int off, int len)
+{
+	Range *r;
+	int x;
+
+	r = nil;
+	if(o->r != nil){
+		if(len < 0)
+			len = o->r->len - off;
+		if(len < 0 || off+len > o->r->len){
+			werrstr("range overflow (len %d) by %d bytes", len, off+len - o->r->len);
+			goto err;
+		}
+		off += o->r->start;
+	}else if(len < 0){
+		len = 0x7fffffff;
+	}
+	if((r = malloc(sizeof(*r))) == nil){
+		werrstr("no memory");
+		goto err;
+	}
+	r->par = o->r;
+	r->start = off;
+	r->len = len;
+	r->prevoff = o->off;
+	if((x = Bseek(o->f, off, 0)) != off){
+		werrstr("seek offset: need %d, got %d", off, x);
+		goto err;
+	}
+	o->off = off;
+	o->r = r;
+	return 0;
+err:
+	free(r);
+	return -1;
+}
+
+static int
+otfpoprange(Otf *o)
+{
+	Range *r;
+	int x;
+
+	r = o->r;
+	if(r == nil){
+		werrstr("pop without push");
+		goto err;
+	}
+	if((x = Bseek(o->f, r->prevoff, 0)) != r->prevoff){
+		werrstr("seek offset: need %d, got %d", r->prevoff, x);
+		goto err;
+	}
+	o->off = r->prevoff;
+	o->r = r->par;
+	free(r);
+	return 0;
+err:
+	return -1;
+}
+
+static u8int *
+otfreadn(Otf *o, int n)
+{
+	Range *r;
+	u8int *b;
+	int x;
+
+	r = o->r;
+	if(r != nil && o->off+n > r->start+r->len){
+		werrstr("need %d at %d, have %d at %d", n, o->off, r->len, r->start);
+		goto err;
+	}
+	if(n > o->bufsz){
+		if((b = realloc(o->buf, n)) == nil){
+			werrstr("no memory");
+			goto err;
+		}
+		o->buf = b;
+		o->bufsz = n;
+	}
+	if((x = Bread(o->f, o->buf, n)) != n){
+		werrstr("need %d, got %d; off %d", n, x, o->off);
+		goto err;
+	}
+	o->off += n;
+
+	return o->buf;
+err:
+	return nil;
+}
+
+static int
+otfarray(Otf *o, void **arr_, void *fun_, int elsz, int num)
+{
+	int i;
+	int (*fun)(Otf*, void*);
+	u8int *arr;
+
+	if((arr = calloc(num, elsz)) == nil){
+		werrstr("no memory");
+		goto err;
+	}
+	fun = fun_;
+	for(i = 0; i < num; i++){
+		if(fun(o, arr + i*elsz) < 0)
+			goto err;
+	}
+	*arr_ = arr;
+	return 0;
+err:
+	free(arr);
+	return -1;
+}
+
+int indentΔ = 2;
+
+static int
+Tfmt(Fmt *f)
+{
+	Tm t;
+	s64int v = va_arg(f->args, s64int);
+	return fmtprint(f, "%τ", tmfmt(tmtime(&t, v, nil), nil));
+}
+
+static int
+Vfmt(Fmt *f)
+{
+	u32int v = va_arg(f->args, u32int);
+	return fmtprint(f, "%d.%d", v>>16, v&0xffff);
+}
+
+static int
+tfmt(Fmt *f)
+{
+	u32int v = va_arg(f->args, u32int);
+	return fmtprint(f, "%c%c%c%c", v>>24, v>>16, v>>8, v>>0);
+}
+
+void
+otfinit(void)
+{
+	tmfmtinstall();
+	fmtinstall('V', Vfmt);
+	fmtinstall('T', Tfmt);
+	fmtinstall('t', tfmt);
+}