shithub: fnt

ref: 3e799de72f01ff3c4b17417da080f4a6901fc59f
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)))

(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. */\n" 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-ptr f)
  (if (field-count f) "*" ""))

(define (field-values f)
  (assoc '= (field-attrs f)))

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

(define (field-fprint-c f)
  (define t (field-type f))
  (define array-loop
    (if (field-count f)
        (λ (lst)
          (list (~a "for(int i = 0; i < v->" (cadr (field-count f)) "; i++){")
                (~a "\tfprint(f, \"%*s%s[%d]:\\n\", indent, \"\", \"" (field-name f) "\", i);")
                (indent lst)
                (~a "}")))
        identity))
  (define array-index (if (field-count f) "[i]" ""))
  (define verb (if (type? t) (if (field-verb f) (cadr (field-verb f)) (type-verb t)) ""))
  (if (field-unused? f)
      empty
      (array-loop (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
                                ");"))))))

(define-struct field (type name attrs)
  #:transparent
  #:methods gen:code
  [(define/generic super-c-type c-type)
   (define (gen-h f)
     (list (~a (if (field-unused? f) "// " "")
               (super-c-type (field-type f))
               " "
               (field-ptr f)
               (field-name f)
               ";")))
   (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) (field-values 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
          (~a "if(ctxarray(ctx, &"
              ref
              ", read_"
              (super-c-type (field-type f))
              ", sizeof("
              (super-c-type (field-type f))
              "), v->"
              (cadr count)
              ") < 0){")]))
     (list*
      (parse-if-error #t)
      (if index
          empty
          (list (~a "\twerrstr(\"%s: %r\", \"" (field-name f) "\");") (~a "\tgoto err;") (~a "}")))
      (match (field-values f)
        [(list '= a ...)
         (list
          (~a "if(" (string-join (map (λ (v) (~a ref " != " (format-number v))) a) " && ") "){")
          (~a "\twerrstr(\"%s: invalid value: %d (0x%ux)\", \""
              (field-name f)
              "\", "
              ref
              ", "
              ref
              ");")
          (~a "\tgoto err;")
          (~a "}"))]
        [#f empty])))])

(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 "struct " (cmplx-name c) " {"))
                      (indent (flatten (map super-gen-h (cmplx-fields c))))
                      (indent (filter-extra (cmplx-extra c) 'field))
                      (list (~a "};")
                            (~a "int read_" (cmplx-name c) "(Ctx *ctx, " (cmplx-name c) " *v);")))))
   (define (gen-c c b index)
     (define field-groups (group-by (λ (f) (cmplx? (field-type f))) (cmplx-fields c)))
     (define (parse-group g)
       (define (p fs index)
         (if (pair? fs)
             (cons (super-gen-c (car fs) "b" index)
                   (p (cdr fs) (+ index (type-size (field-type (car fs))))))
             empty))
       (p g 0))
     (define (gen-group-c fields)
       (if (cmplx? (field-type (car fields)))
           (map (λ (f) (super-gen-c f #f #f)) fields)
           (let ([sum (apply + (map (λ (f) (type-size (field-type f))) fields))])
             (list* (~a "if((b = ctxreadn(ctx, " sum ")) == nil)")
                    (~a "\tgoto err;")
                    (parse-group fields)))))
     (flatten
      (append (list (~a "int")
                    (~a "read_" (cmplx-name c) "(Ctx *ctx, " (cmplx-name c) " *v)")
                    (~a "{")
                    (~a "\tu8int *b;"))
              (indent (flatten (map gen-group-c field-groups)))
              (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 "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])
    (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 "]" 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
    [(_ {~literal hex}) #''(verb "%#ux")]
    [(_ ({~literal =} vs:number ...+)) #''(= 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 (= 0 1 2 3 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}
                   {LONGDATETIME created}
                   {LONGDATETIME modified}
                   {int16 xMin}
                   {int16 yMin}
                   {int16 xMax}
                   {int16 yMax}
                   {uint16 macStyle}
                   {uint16 lowestRecPPEM}
                   {int16 fontDirectionHint unused (= -2 -1 0 1 2)}
                   {int16 indexToLocFormat (= 0 1)}
                   {int16 glyphDataFormat unused (= 0)})
         #:tag "head")

(mkcmplx TableMaxp
         (mkfields {Version16Dot16 version hex (= #x05000 #x10000)}
                   {uint16 numGlyphs} ; FIXME there are more fields here, depending on the version
                   )
         #:tag "maxp")

(mkcmplx TableRecord
         (mkfields {Tag tableTag} {uint32 checksum 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 identity)

(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) " = malloc(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
        )