shithub: fnt

ref: a73cbb099ae671c0d68709a96876b0940370e37d
dir: /otf.rkt/

View raw version
#!/usr/bin/env racket
#lang racket

(require (for-syntax racket/format))
(require (for-syntax syntax/parse))
(require (for-syntax racket/contract))

(require racket/contract)
(require racket/generic)

(define types '()) ; base types set
(define cmplxs '()) ; complex types set
(define tagged '()) ; complex types set that also have a defined tag

; types allowed to be used as index
(define-for-syntax (type-index? type) (member type '(uint16 uint32)))

(define size-in-bits/c
  (make-contract #:name 'size-in-bits/c #:first-order (λ (x) (member x '(8 16 24 32 40 48 64)))))

(define tag/c
  (make-contract #:name 'tag/c #:first-order (λ (x) (and (string? x) (= 4 (string-length x))))))

(define/contract (indent lst)
  (-> (listof string?) (listof string?))
  (map (λ (str) (string-append "\t" str)) lst))

(define/contract (c-typedef? s)
  (-> string? boolean?)
  (string-prefix? s "typedef"))

(define/contract (format f)
  (-> procedure? string?)
  (define-values (a b) (partition c-typedef? (flatten (map f cmplxs))))
  (define ps (list "/* this file is generated. do not modify. */" a b (map f types) ""))
  (string-join (flatten ps) "\n"))

(define-generics code
                 (gen-h code) ; generates code for the C header
                 (gen-c code b index) ; generates code for the C source file
                 (c-type code)) ; type name to be used in C

(define-struct type (name bits c verb parse)
  #:transparent
  #:methods gen:code
  [(define (gen-h t)
     empty)
   (define (gen-c t b index)
     empty)
   (define (c-type t)
     (type-c t))])

(define/contract (type-size t)
  (-> type? positive?)
  (/ (type-bits t) 8))

(define (field-unused? f)
  (assoc 'unused (field-attrs f)))

(define (field-count f)
  (assoc 'count (field-attrs f)))

(define (field-test f)
  (filter (λ (t) (eq? (car t) 'test)) (field-attrs f)))

(define (field-verb f)
  (assoc 'verb (field-attrs f)))

(define (field-cond f)
  (assoc 'cond (field-attrs f)))

(define (block stmt lst)
  (if (= (length lst) 1) (list* stmt lst) (list (string-append stmt "{") lst "}")))

(define (wrap-cond-c cond lst)
  (match cond
    [#f lst]
    [(list 'cond op ref n ...)
     (block (~a "if(" (string-join (map (λ (n) (~a "v->" ref " " op " " n)) n) " || ") ")")
            (indent (flatten lst)))]))

(define (field-fprint-c f)
  (define t (field-type f))
  (define count (field-count f))
  (define end (and count (if (cmplx? t) (~a "v->" (cadr count)) (cadr count))))
  (define basic-array (and (type? t) end))
  (define fixed-array (and basic-array (number? end)))
  (define array-index (if (field-count f) "[i]" ""))
  (define verb (if (type? t) (if (field-verb f) (cadr (field-verb f)) (type-verb t)) ""))
  (define fprint-index
    (if basic-array
        (~a "fprint(f, \"%*s%s[%d]: "
            verb
            "\\n\", indent, \"\", \""
            (field-name f)
            "\", i, v->"
            (field-name f)
            array-index
            ");")
        (~a "fprint(f, \"%*s%s[%d]:\\n\", indent, \"\", \"" (field-name f) "\", i);")))
  (define array-loop
    (if count
        (λ (lst)
          (block (~a "for(int i = 0; i < " (if fixed-array "" "v->") (cadr count) "; i++)")
                 (indent (list* fprint-index (if basic-array empty lst)))))
        identity))
  (define lst
    (list
     (if (type? t)
         (~a "fprint(f, \"%*s%s: "
             verb
             "\\n\", indent, \"\", \""
             (field-name f)
             "\", v->"
             (field-name f)
             array-index
             ");")
         (~a "fprint_" (cmplx-name t) "(f, indent+indentΔ, &v->" (field-name f) array-index ");"))))
  (if (field-unused? f) empty (wrap-cond-c (field-cond f) (array-loop lst))))

(define (invert-c op)
  (match op
    ['== '!=]
    ['<= '>]
    ['>= '<]
    ['< '>=]
    ['> '<=]))

(define-struct field (type name attrs)
  #:transparent
  #:methods gen:code
  [(define/generic super-c-type c-type)
   (define (gen-h f)
     (define cnt (field-count f))
     (define fixed-array (and cnt (number? (cadr cnt))))
     (list (~a (if (field-unused? f) "// " "")
               (super-c-type (field-type f))
               " "
               (if (and cnt (not fixed-array)) "*" "")
               (field-name f)
               (if fixed-array (~a "[" (cadr cnt) "]") "")
               ";")))
   (define (gen-c f b index)
     (define (size t)
       (if (type? t) (type-size t) 0))
     (define (name t)
       (if (type? t) (super-c-type t) (cmplx-name t)))
     (define ref (~a (if (field-unused? f) "" "v->") (field-name f)))
     (define declared (and (field-unused? f) (not (empty? (field-test f)))))
     (define (format-number x)
       (if (<= x 32768) (~r x) (~r x #:base 16 #:sign '("0x" "" "-0x"))))
     (define (parse-if-error read)
       (match (field-count f)
         [#f
          (if (or declared (not (field-unused? f)))
              (if index
                  (~a (if declared (~a (name (field-type f)) " ") "")
                      ref
                      " = "
                      ((type-parse (field-type f)) b index)
                      ";")
                  (list (if declared (~a (name (field-type f)) " " ref ";") empty)
                        (~a "if(read_" (name (field-type f)) "(ctx, &" ref ") < 0){")))
              (if index empty (~a "if(skip_bytes(ctx, " (size (field-type f)) ") < 0){")))]
         [count
          #:when (type? (field-type f))
          (if (field-unused? f)
              empty
              (list
               (~a "for(int i = 0; i < " (if (number? (cadr count)) "" "v->") (cadr count) "; i++)")
               (~a "\t"
                   ref
                   "[i] = "
                   ((type-parse (field-type f)) b index (~a "i*" (size (field-type f))))
                   ";")))]
         [count
          (~a "if(ctxarray(ctx, &"
              ref
              ", read_"
              (super-c-type (field-type f))
              ", sizeof("
              (super-c-type (field-type f))
              "), v->"
              (cadr count)
              ") < 0){")]))
     (define (test-cond ts)
       (map (λ (t)
              (match t
                [(list 'test op a ...)
                 (string-join (map (λ (v) (~a ref " " (invert-c op) " " (format-number v))) a)
                              " && ")]))
            ts))
     (list*
      (parse-if-error #t)
      (if index
          empty
          (list (~a "\twerrstr(\"%s: %r\", \"" (field-name f) "\");") (~a "\tgoto err;") (~a "}")))
      (match (test-cond (field-test f))
        [(list) empty]
        [(list a ...)
         (list (~a "if(" (string-join a " || ") "){")
               (~a "\twerrstr(\"%s: invalid value: %d (0x%ux)\", \""
                   (field-name f)
                   "\", "
                   ref
                   ", "
                   ref
                   ");")
               (~a "\tgoto err;")
               (~a "}"))])))])

(define (filter-extra extra key)
  (flatten (filter-map (λ (e) (and (eq? (car e) key) (cdr e))) extra)))

(define-struct cmplx (name fields tag extra)
  #:transparent
  #:methods gen:code
  [(define/generic super-gen-h gen-h)
   (define/generic super-gen-c gen-c)
   (define (gen-h c)
     (flatten (append (list (~a "typedef struct " (cmplx-name c) " " (cmplx-name c) ";")
                            (~a "")
                            (~a "struct " (cmplx-name c) " {"))
                      (indent (flatten (map super-gen-h (cmplx-fields c))))
                      (indent (filter-extra (cmplx-extra c) 'field))
                      (list (~a "};")
                            (~a "")
                            (~a "int read_" (cmplx-name c) "(Ctx *ctx, " (cmplx-name c) " *v);")))))
   (define (gen-c c b index)
     (define (no-vla? f)
       (define cnt (field-count f))
       (or (not cnt) (number? (cadr cnt))))
     ; group fields to minimize number of reads
     ; complex types are always alone
     ; simple types can be combined unless versioned
     ; versioned simple types are combined if the check is the same
     (define (group-fields fields)
       (define (combine? f g)
         (or (empty? g)
             (and (type? (field-type f))
                  (type? (field-type (car g)))
                  (no-vla? f)
                  (no-vla? (car g))
                  (equal? (field-cond f) (field-cond (car g))))))
       (define (group- g fields)
         (match fields
           [(list-rest f tail)
            (if (combine? f g) (group- (cons f g) tail) (cons (reverse g) (group- (list f) tail)))]
           [_ (list (reverse g))]))
       (group- empty fields))
     (define (field-size f)
       (match (field-count f)
         [#f (type-size (field-type f))]
         [count
          #:when (type? (field-type f))
          ((if (number? (cadr count))
               (λ (sz) (* (cadr count) sz))
               (λ (sz) (~a "v->" (cadr count) "*" sz)))
           (type-size (field-type f)))]))
     (define (add x y)
       (match (list x y)
         [(list _ 0) x]
         [(list 0 _) y]
         [_ (if (and (number? x) (number? y)) (+ x y) (~a x "+" y))]))
     (define (parse-group g)
       (define (p fs index)
         (if (pair? fs)
             (cons (super-gen-c (car fs) "b" index) (p (cdr fs) (add index (field-size (car fs)))))
             empty))
       (p g 0))
     (define (gen-group-c 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 = ctxreadn(ctx, " sum ")) == nil)")
                                                    (~a "\tgoto err;")
                                                    (parse-group fields)))])
                          lst))))
     (flatten
      (append (list (~a "")
                    (~a "int")
                    (~a "read_" (cmplx-name c) "(Ctx *ctx, " (cmplx-name c) " *v)")
                    (~a "{")
                    (~a "\tu8int *b;"))
              (indent (flatten (map gen-group-c (group-fields (cmplx-fields c)))))
              (indent (filter-extra (cmplx-extra c) 'read))
              (list (~a "\treturn 0;")
                    (~a "err:")
                    (~a "\twerrstr(\"%s: %r\", \"" (cmplx-name c) "\");")
                    (~a "\treturn -1;")
                    (~a "}"))
              (list (~a "")
                    (~a "void")
                    (~a "fprint_" (cmplx-name c) "(int f, int indent, " (cmplx-name c) " *v)")
                    (~a "{")
                    (indent (flatten (map field-fprint-c (cmplx-fields c))))
                    (indent (filter-extra (cmplx-extra c) 'fprint))
                    (~a "}")))))
   (define (c-type c)
     (cmplx-name c))])

(define/contract (autoparse bits ctype)
  (-> positive? symbol? procedure?)
  (λ (b [index 0] [offset #f])
    (define off (if offset (~a "+" offset) ""))
    (define cast (~a "(" ctype ")"))
    (define (f index bits)
      (define sh (- bits 8))
      (define tail (if (positive? sh) (~a "<<" sh " | " (f (add1 index) sh)) ""))
      (~a (if (> sh 24) cast "") b "[" index off "]" tail))
    (f index bits)))

(define-syntax (mktype stx)
  (syntax-parse stx
    [(_ typ:id bits c:id verb:string)
     #'(begin
         (define parse (autoparse bits `c))
         (mktype typ bits c verb parse))]
    [(_ typ:id bits c:id verb:string parse:expr)
     #:declare bits (expr/c #'size-in-bits/c #:name "size in bits")
     #'(begin
         (define typ (make-type `typ bits.c `c verb parse))
         (set! types (append types (list typ))))]))

(define-syntax (mkcmplx stx)
  (syntax-parse stx
    [(_ typ:id fields:expr (~optional (~seq #:tag tag)) (~optional (~seq #:extra extra:expr)))
     #:declare tag (expr/c #'tag/c #:name "table tag")
     #'(begin
         (define tag- (~? (~@ tag.c) #f))
         (define typ (make-cmplx `typ fields tag- (~? (~@ extra) empty)))
         (set! cmplxs (append cmplxs (list typ)))
         (when tag-
           (set! tagged (append tagged (list typ)))))]))

(define-for-syntax fields '())

(define-syntax (mkattr stx)
  (syntax-parse stx
    [(_ (n:number)) #''(count n)]
    [(_ {~literal hex}) #''(verb "%#ux")]
    [(_ (p:expr vs:number ...+)) #''(test p vs ...)]
    [(_ (p:expr ref:id vs:number ...+))
     #''(cond
          p
          ref
          vs ...)]
    [(_ ({~literal count} n:id))
     (begin
       (define counter (assoc (syntax->datum #`n) fields))
       (cond
         [(pair? counter)
          (if (type-index? (cadr counter))
              #''(count n)
              (raise-syntax-error #f
                                  (~a "type " (cadr counter) " can't be used as index to the array")
                                  stx
                                  #'n))]
         [else (raise-syntax-error #f "no such field" stx #'n)]))]
    [(_ {~literal unused}) #''(unused #t)]))

(define-syntax (mkfield stx)
  (syntax-parse stx
    [(_ type:id name:id attrs ...)
     (begin
       (let ([f #'(field type
                         `name
                         (list (mkattr [~@ attrs]) ...))])
         (begin
           (set! fields (append fields (list (syntax->datum #'(name type)))))
           f)))]))

(define-syntax (mkfields stx)
  (syntax-parse stx
    [(_ x ...+)
     (begin
       (set! fields '())
       #'(list (mkfield [~@ . x]) ...))]))

(mktype uint8 8 u8int "%ud")
(mktype int8 8 s8int "%d")
(mktype uint16 16 u16int "%ud")
(mktype int16 16 s16int "%d")
(mktype uint24 24 u32int "%ud")
(mktype uint32 32 u32int "%ud")
(mktype int32 32 s32int "%d")
(mktype FWORD 16 s16int "%d")
(mktype UFWORD 16 u16int "%ud")
(mktype LONGDATETIME
        64
        s64int
        "%T"
        (λ (b index) (~a "(" ((autoparse 64 's64int) b index) ") - 2082844800LL")))
(mktype Tag 32 u32int "%t")
(mktype Offset16 16 u16int "%ud")
(mktype Offset24 24 u32int "%ud")
(mktype Offset32 32 u32int "%ud")
(mktype Version16Dot16 32 u32int "%V")
(mktype Fixed 32 float "%g" (λ (b index) (~a "(" ((type-parse int32) b index) ")/65536.0f")))
(mktype F2DOT14
        16
        float
        "%g"
        (λ (b index)
          (define x (~a ((type-parse int16) b index)))
          (~a "(" x ">>14)+(" x "&((1<<14)-1))/16384.0")))

(mkcmplx EncodingRecord
         (mkfields {uint16 platformID (<= 4)} {uint16 encodingID} {Offset32 subtableOffset}))

(mkcmplx TableCmap
         (mkfields {uint16 version unused (== 0)}
                   {uint16 numTables}
                   {EncodingRecord encodingRecords (count numTables)})
         #:tag "cmap")

(mkcmplx TableHead
         (mkfields {uint16 majorVersion unused (== 1)}
                   {uint16 minorVersion unused (== 0)}
                   {Fixed fontRevision unused}
                   {uint32 checksumAdjustment unused}
                   {uint32 magicNumber unused (== #x5f0f3cf5)}
                   {uint16 flags}
                   {uint16 unitsPerEm (>= 16) (<= 16384)}
                   {LONGDATETIME created}
                   {LONGDATETIME modified}
                   {int16 xMin}
                   {int16 yMin}
                   {int16 xMax}
                   {int16 yMax}
                   {uint16 macStyle}
                   {uint16 lowestRecPPEM}
                   {int16 fontDirectionHint unused (>= -2) (<= 2)}
                   {int16 indexToLocFormat (<= 1)}
                   {int16 glyphDataFormat unused (== 0)})
         #:tag "head")

(mkcmplx TableHhea
         (mkfields {uint16 majorVersion (== 1)}
                   {uint16 minorVersion (== 0)}
                   {FWORD ascender}
                   {FWORD descender}
                   {FWORD lineGap}
                   {UFWORD advanceWidthMax}
                   {FWORD minLeftSideBearing}
                   {FWORD minRightSideBearing}
                   {FWORD xMaxExtent}
                   {int16 caretSlopeRise}
                   {int16 caretSlopeRun}
                   {int16 caretOffset}
                   {int16 reserved [4] unused}
                   {int16 metricDataFormat (== 0)}
                   {uint16 numberOfHMetrics})
         #:tag "hhea")

(mkcmplx LongHorMetric (mkfields {UFWORD advanceWidth} {FWORD lsb}))

#|
FIXME what. WHAT.
(mkcmplx TableHmtx
         (mkfields {LongHorMetric hMetrics (count (TableHhea numberOfHMetrics))}
                   {FWORD leftSideBearings (count (- (TableMaxp numGlyphs) (TableHhea numberOfHMetrics)))}))
|#

(mkcmplx TableMaxp
         (mkfields {Version16Dot16 version (== #x05000 #x10000) unused}
                   {uint16 numGlyphs}
                   ; a bunch of fields ignored here
                   )
         #:tag "maxp")

(mkcmplx TablePost
         (mkfields {Version16Dot16 version (== #x10000 #x20000 #x25000 #x30000) unused}
                   {Fixed italicAngle}
                   {FWORD underlinePosition}
                   {FWORD underlineThickness}
                   {uint32 isFixedPitch}
                   ; a bunch of fields ignored here
                   )
         #:tag "post")

(mkcmplx NameRecord
         (mkfields {uint16 platformID}
                   {uint16 encodingID}
                   {uint16 languageID}
                   {uint16 nameID}
                   {uint16 length}
                   {Offset16 stringOffset}))

(mkcmplx LangTagRecord (mkfields {uint16 length} {Offset16 langTagOffset}))

(mkcmplx TableName
         (mkfields {uint16 version (== 0 1)}
                   {uint16 count}
                   {Offset16 storageOffset}
                   {NameRecord nameRecord (count count)}
                   {uint16 langTagCount (>= version 1)}
                   {LangTagRecord langTagRecord (count langTagCount) (>= version 1)})
         #:tag "name")

(mkcmplx BigGlyphMetrics
         (mkfields {uint8 height}
                   {uint8 width}
                   {int8 horiBearingX}
                   {int8 horiBearingY}
                   {uint8 horiAdvance}
                   {int8 vertBearingX}
                   {int8 vertBearingY}
                   {uint8 vertAdvance}))

(mkcmplx SmallGlyphMetrics
         (mkfields {uint8 height} {uint8 width} {int8 bearingX} {int8 bearingY} {uint8 advance}))

(mkcmplx SbitLineMetrics
         (mkfields {int8 ascender}
                   {int8 descender}
                   {uint8 widthMax}
                   {int8 caretSlopeNumerator}
                   {int8 caretSlopeDenumerator}
                   {int8 caretOffset}
                   {int8 minOriginSB}
                   {int8 minAdvanceSB}
                   {int8 maxBeforeBL}
                   {int8 minAfterBL}
                   {int8 pad [2] unused}))

(mkcmplx BitmapSize
         (mkfields {Offset32 indexSubtableListOffset}
                   {uint32 indexSubtableListSize}
                   {uint32 numberOfIndexSubtables}
                   {uint32 colorRef unused}
                   {SbitLineMetrics hori}
                   {SbitLineMetrics vert}
                   {uint16 startGlyphIndex}
                   {uint16 endGlyphIndex}
                   {uint8 ppemX}
                   {uint8 ppemY}
                   {uint8 bitDepth}
                   {int8 flags}))

(mkcmplx TableEBLC
         (mkfields {uint16 majorVersion (== 2) unused}
                   {uint16 minorVersion (== 0) unused}
                   {uint32 numSizes}
                   {BitmapSize bitmapSizes (count numSizes)})
         #:tag "EBLC")

(mkcmplx AttachList
         (mkfields {Offset16 coverageOffset}
                   {uint16 glyphCount}
                   {Offset16 attachPointOffsets (count glyphCount)}))

(mkcmplx AttachPoint (mkfields {uint16 pointCount} {uint16 pointIndices (count pointCount)}))

(mkcmplx LigCaretList
         (mkfields {Offset16 coverageOffset}
                   {uint16 ligGlyphCount}
                   {Offset16 ligGlyphOffsets (count ligGlyphCount)}))

(mkcmplx LigGlyph (mkfields {uint16 caretCount} {Offset16 caretValueOffsets (count caretCount)}))

(mkcmplx CaretValue
         (mkfields {uint16 format (>= 1) (<= 3)}
                   {int16 coordinate (== format 1 3)}
                   {uint16 caretValuePointIndex (== format 2)}
                   {Offset16 deviceOffset (== format 3)}))

(mkcmplx ValueRecord
         (mkfields {int16 xPlacement}
                   {int16 yPlacement}
                   {int16 xAdvance}
                   {int16 yAdvance}
                   {Offset16 xPlaDeviceOffset}
                   {Offset16 yPlaDeviceOffset}
                   {Offset16 xAdvDeviceOffset}
                   {Offset16 yAdvDeviceOffset}))

(mkcmplx SinglePos
         (mkfields {uint16 format (== 1 2)}
                   {Offset16 coverageOffset}
                   {uint16 valueFormat}
                   {ValueRecord valueRecord (== format 1)}
                   {uint16 valueCount (== format 2)}
                   {ValueRecord valueRecords (== format 2) (count valueCount)}))

(mkcmplx TableGDEF
         (mkfields {uint16 majorVersion (== 1) unused}
                   {uint16 minorVersion (== 0 2 3)}
                   {Offset16 glyphClassDefOffset}
                   {Offset16 attachListOffset}
                   {Offset16 ligCaretListOffset}
                   {Offset16 markAttachClassDefOffset}
                   {Offset16 markGlyphSetsDefOffset (>= minorVersion 2)}
                   {Offset32 itemVarStoreOffset (>= minorVersion 3)})
         #:tag "GDEF")

(mkcmplx TableGPOS
         (mkfields {uint16 majorVersion (== 1) unused}
                   {uint16 minorVersion (<= 1)}
                   {Offset16 scriptListOffset}
                   {Offset16 featureListOffset}
                   {Offset16 lookupListOffset}
                   {Offset32 featureVariationsOffset (== minorVersion 1)})
         #:tag "GPOS")

(mkcmplx TableGSUB
         (mkfields {uint16 majorVersion (== 1) unused}
                   {uint16 minorVersion (<= 1)}
                   {Offset16 scriptListOffset}
                   {Offset16 featureListOffset}
                   {Offset16 lookupListOffset}
                   {Offset32 featureVariationsOffset (== minorVersion 1)})
         #:tag "GSUB")

(mkcmplx TableMATH
         (mkfields {uint16 majorVersion (== 1) unused}
                   {uint16 minorVersion (== 0) unused}
                   {Offset16 mathConstantsOffset}
                   {Offset16 mathGlyphInfoOffset}
                   {Offset16 mathVariantsOffset})
         #:tag "MATH")

(mkcmplx MathValueRecord (mkfields {FWORD value} {Offset16 deviceOffset}))

(mkcmplx MathConstants
         (mkfields {int16 scriptPercentScaleDown}
                   {int16 scriptScriptPercentScaleDown}
                   {UFWORD delimitedSubFormulaMinHeight}
                   {UFWORD displayOperatorMinHeight}
                   {MathValueRecord mathLeading}
                   {MathValueRecord axisHeight}
                   {MathValueRecord accentBaseHeight}
                   {MathValueRecord flattenedAccentBaseHeight}
                   {MathValueRecord subscriptShiftDown}
                   {MathValueRecord subscriptTopMax}
                   {MathValueRecord subscriptBaselineDropMin}
                   {MathValueRecord superscriptShiftUp}
                   {MathValueRecord superscriptShiftUpCramped}
                   {MathValueRecord superscriptBottomMin}
                   {MathValueRecord superscriptBaselineDropMax}
                   {MathValueRecord subSuperscriptGapMin}
                   {MathValueRecord superscriptBottomMaxWithSubscript}
                   {MathValueRecord spaceAfterScript}
                   {MathValueRecord upperLimitGapMin}
                   {MathValueRecord upperLimitBaselineRiseMin}
                   {MathValueRecord lowerLimitGapMin}
                   {MathValueRecord lowerLimitBaselineDropMin}
                   {MathValueRecord stackTopShiftUp}
                   {MathValueRecord stackTopDisplayStyleShiftUp}
                   {MathValueRecord stackBottomShiftDown}
                   {MathValueRecord stackBottomDisplayStyleShiftDown}
                   {MathValueRecord stackGapMin}
                   {MathValueRecord stackDisplayStyleGapMin}
                   {MathValueRecord stretchStackTopShiftUp}
                   {MathValueRecord stretchStackBottomShiftDown}
                   {MathValueRecord stretchStackGapAboveMin}
                   {MathValueRecord stretchStackGapBelowMin}
                   {MathValueRecord fractionNumeratorShiftUp}
                   {MathValueRecord fractionNumeratorDisplayStyleShiftUp}
                   {MathValueRecord fractionDenominatorShiftDown}
                   {MathValueRecord fractionDenominatorDisplayStyleShiftDown}
                   {MathValueRecord fractionNumeratorGapMin}
                   {MathValueRecord fractionNumDisplayStyleGapMin}
                   {MathValueRecord fractionRuleThickness}
                   {MathValueRecord fractionDenominatorGapMin}
                   {MathValueRecord fractionDenomDisplayStyleGapMin}
                   {MathValueRecord skewedFractionHorizontalGap}
                   {MathValueRecord skewedFractionVerticalGap}
                   {MathValueRecord overbarVerticalGap}
                   {MathValueRecord overbarRuleThickness}
                   {MathValueRecord overbarExtraAscender}
                   {MathValueRecord underbarVerticalGap}
                   {MathValueRecord underbarRuleThickness}
                   {MathValueRecord underbarExtraDescender}
                   {MathValueRecord radicalVerticalGap}
                   {MathValueRecord radicalDisplayStyleVerticalGap}
                   {MathValueRecord radicalRuleThickness}
                   {MathValueRecord radicalExtraAscender}
                   {MathValueRecord radicalKernBeforeDegree}
                   {MathValueRecord radicalKernAfterDegree}
                   {int16 radicalDegreeBottomRaisePercent}))

(mkcmplx MathGlyphInfo
         (mkfields {Offset16 mathItalicsCorrectionInfoOffset}
                   {Offset16 mathTopAccentAttachmentOffset}
                   {Offset16 extendedShapeCoverageOffset}
                   {Offset16 mathKernInfoOffset}))

(mkcmplx MathItalicsCorrectionInfo
         (mkfields {Offset16 italicsCorrectionCoverageOffset}
                   {uint16 italicsCorrectionCount}
                   {MathValueRecord italicsCorrection (count italicsCorrectionCount)}))

(mkcmplx MathTopAccentAttachment
         (mkfields {Offset16 topAccentCoverageOffset}
                   {uint16 topAccentAttachmentCount}
                   {MathValueRecord topAccentAttachment (count topAccentAttachmentCount)}))

(mkcmplx MathKernInfoRecord
         (mkfields {Offset16 topRightMathKernOffset}
                   {Offset16 topLeftMathKernOffset}
                   {Offset16 bottomRightMathKernOffset}
                   {Offset16 bottomLeftMathKernOffset}))

(mkcmplx MathKernInfo
         (mkfields {Offset16 mathKernCoverageOffset}
                   {uint16 mathKernCount}
                   {MathKernInfoRecord mathKernInfoRecords (count mathKernCount)}))

(mkcmplx MathKern
         (mkfields {uint16 heightCount}
                   {MathValueRecord correctionHeight (count heightCount)}
                   {MathValueRecord kernValues (count heightCount)}))

(mkcmplx MathVariants
         (mkfields {UFWORD minConnectorOverlap}
                   {Offset16 vertGlyphCoverageOffset}
                   {Offset16 horizGlyphCoverageOffset}
                   {uint16 vertGlyphCount}
                   {uint16 horizGlyphCount}
                   {Offset16 vertGlyphConstructionOffsets (count vertGlyphCount)}
                   {Offset16 horizGlyphConstructionOffsets (count horizGlyphCount)}))

(mkcmplx MathGlyphVariantRecord (mkfields {uint16 variantGlyph} {UFWORD advanceMeasurement}))

(mkcmplx MathGlyphConstruction
         (mkfields {Offset16 glyphAssemblyOffset}
                   {uint16 variantCount}
                   {MathGlyphVariantRecord mathGlyphVariantRecords (count variantCount)}))

(mkcmplx GlyphPart
         (mkfields {uint16 glyphID}
                   {UFWORD startConnectorLength}
                   {UFWORD endConnectorLength}
                   {UFWORD fullAdvance}
                   {uint16 partFlags}))

(mkcmplx GlyphAssembly
         (mkfields {MathValueRecord italicsCorrection}
                   {uint16 partCount}
                   {GlyphPart partRecords (count partCount)}))

(mkcmplx TableOS∕2
         (mkfields {uint16 version (<= 5)}
                   {FWORD xAvgCharWidth}
                   {uint16 usWeightClass}
                   {uint16 usWidthClass}
                   {uint16 fsType}
                   {FWORD ySubscriptXSize}
                   {FWORD ySubscriptYSize}
                   {FWORD ySubscriptXOffset}
                   {FWORD ySubscriptYOffset}
                   {FWORD ySuperscriptXSize}
                   {FWORD ySuperscriptYSize}
                   {FWORD ySuperscriptXOffset}
                   {FWORD ySuperscriptYOffset}
                   {FWORD yStrikeoutSize}
                   {FWORD yStrikeoutPosition}
                   {int16 sFamilyClass}
                   {uint8 panose [10]}
                   {uint32 ulUnicodeRange1 hex}
                   {uint32 ulUnicodeRange2 hex}
                   {uint32 ulUnicodeRange3 hex}
                   {uint32 ulUnicodeRange4 hex}
                   {Tag achVendID}
                   {uint16 fsSelection}
                   {uint16 usFirstCharIndex}
                   {uint16 usLastCharIndex}
                   {FWORD sTypoAscender}
                   {FWORD sTypoDescender}
                   {FWORD sTypoLineGap}
                   {UFWORD usWinAscent}
                   {UFWORD usWinDescent}
                   {uint32 ulCodePageRange1 (>= version 1) hex}
                   {uint32 ulCodePageRange2 (>= version 1) hex}
                   {FWORD sxHeight (>= version 2)}
                   {FWORD sCapHeight (>= version 2)}
                   {uint16 usDefaultChar (>= version 2) hex}
                   {uint16 usBreakChar (>= version 2) hex}
                   {uint16 usMaxContext (>= version 2)}
                   {uint16 usLowerOpticalPointSize (>= version 5)}
                   {uint16 usUpperOpticalPointSize (>= version 5)})
         #:tag "OS/2")

(mkcmplx TableRecord
         (mkfields {Tag tableTag} {uint32 checksum unused hex} {Offset32 offset} {uint32 length})
         #:extra (list (cons 'field
                             (list (~a "void *parsed;")
                                   (~a "void (*fprint)(int f, int indent, void *parsed);")))
                       (cons 'fprint
                             (list (~a "if(v->fprint != nil && v->parsed != nil)")
                                   (~a "\tv->fprint(f, indent+indentΔ, v->parsed);")))))

(define (c-friendly-name t)
  (string-replace (string-trim (string-downcase t)) "/" "∕"))

(define (tagged-tables-fields tagged)
  (define (ptr c)
    (c-friendly-name (cmplx-tag c)))
  (define (case-statement c)
    (define tag (cmplx-tag c))
    (define (ft t i)
      (~a "'" (string-ref t i) "'" (if (< i 3) (~a "<<" (* 8 (- 3 i))) "")))
    (define case-tag (~a "(u32int)(" (string-join (map (λ (i) (ft tag i)) (range 4)) "|") ")"))
    (list (~a "\tcase " case-tag ":")
          (~a "\t\tv->" (ptr c) " = calloc(1, sizeof(" (cmplx-name c) "));")
          (~a "\t\tif(read_" (cmplx-name c) "(ctx, v->" (ptr c) ") < 0){")
          (~a "\t\t\twerrstr(\"%s: %r\", \"" tag "\");")
          (~a "\t\t\tfree(v->" (ptr c) ");")
          (~a "\t\t\tgoto err;")
          (~a "\t\t}")
          (~a "\t\trec->parsed = v->" (ptr c) ";")
          (~a "\t\trec->fprint = (void*)fprint_" (cmplx-name c) ";")
          (~a "\t\tbreak;")))
  (list (cons 'field (map (λ (c) (~a (cmplx-name c) " *" (ptr c) ";")) tagged))
        (cons 'read
              (list (~a "for(int i = 0; i < v->numTables; i++){")
                    (~a "\tTableRecord *rec = &v->tableRecords[i];")
                    (~a "\tif(rec->length == 0)") ; skip all empty tables
                    (~a "\t\tcontinue;")
                    (~a "\tif(ctxpushrange(ctx, rec->offset, rec->length) < 0)")
                    (~a "\t\tgoto err;")
                    (~a "\tswitch(rec->tableTag){")
                    (map case-statement tagged)
                    (~a "\t}")
                    (~a "\tctxpoprange(ctx);")
                    (~a "}")))))

(mkcmplx TableDirectory
         (mkfields {uint32 sfntVersion (== #x00010000 #x4f54544f) hex}
                   {uint16 numTables}
                   {uint16 searchRange}
                   {uint16 entrySelector}
                   {uint16 rangeShift}
                   {TableRecord tableRecords (count numTables)})
         #:extra (tagged-tables-fields tagged))

(printf (format gen-h))
(printf #<<EOF

extern int indentΔ;

#pragma varargck type "T" s64int
#pragma varargck type "t" u32int
#pragma varargck type "V" u32int

void otfinit(void);

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
        )