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