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);+}
--
⑨