#lang debug racket/base (require (for-syntax racket/base) racket/match racket/list racket/dict "struct.rkt" xenomorph racket/struct) (provide (all-defined-out)) #| approximates https://github.com/mbutterick/fontkit/blob/master/src/glyph/TTFGlyph.js |# ;; The header for both simple and composite glyphs (define GlyfHeader (x:struct 'numberOfContours int16be ;; if negative, this is a composite glyph 'xMin int16be 'yMin int16be 'xMax int16be 'yMax int16be)) ;; Flags for simple glyphs (define-syntax (define-flag-series stx) (syntax-case stx () [(_ . IDS) #`(match-define (list . IDS) (map (λ (x) (expt 2 x)) (range #,(length (syntax->list #'IDS)))))])) ;; Flags for simple glyphs (define-flag-series ON_CURVE X_SHORT_VECTOR Y_SHORT_VECTOR REPEAT SAME_X SAME_Y) ;; Flags for composite glyphs (define-flag-series ARG_1_AND_2_ARE_WORDS ARGS_ARE_XY_VALUES ROUND_XY_TO_GRID WE_HAVE_A_SCALE __EMPTY-FLAG___ MORE_COMPONENTS WE_HAVE_AN_X_AND_Y_SCALE WE_HAVE_A_TWO_BY_TWO WE_HAVE_INSTRUCTIONS USE_MY_METRICS OVERLAP_COMPOUND SCALED_COMPONENT_OFFSET UNSCALED_COMPONENT_OFFSET) ;; Represents a point in a simple glyph (struct ttf-glyph-point (on-curve end-contour x y) #:transparent #:mutable) (define (+tff-glyph-point on-curve end-contour [x 0] [y 0]) (ttf-glyph-point on-curve end-contour x y)) (define (copy pt) (apply +tff-glyph-point (struct->list pt))) ;; Represents a component in a composite glyph (struct ttf-glyph-component (glyph-id dx dy pos scale-x scale-y scale-01 scale-10) #:transparent #:mutable) (define (+ttf-glyph-component glyph-id dx dy [pos 0] [scale-x 1] [scale-y 1] [scale-01 0] [scale-10 0]) (ttf-glyph-component glyph-id dx dy pos scale-x scale-y scale-01 scale-10)) ;; Parses just the glyph header and returns the bounding box #;(define/override (_getCBox internal) (unfinished)) ;; Parses a single glyph coordinate (define (parse-glyph-coord port prev short same) (unless (input-port? port) (raise-argument-error 'parse-glyph-coord "input port" port)) (unless (number? prev) (raise-argument-error 'parse-glyph-coord "number" prev)) (unless (and (boolean? short) (boolean? same)) (raise-argument-error 'parse-glyph-coord "booleans" (list short same))) (+ prev (if short ((if (not same) - +) (decode uint8 port)) (if same 0 (decode int16be port))))) ;; Decodes the glyph data into points for simple glyphs, ;; or components for composite glyphs (require "table-stream.rkt") (define (glyph-decode ttfg) (define offsets (hash-ref (get-table (glyph-font ttfg) 'loca) 'offsets)) (match-define (list glyfPos nextPos) (take (drop offsets (glyph-id ttfg)) 2)) ;; Nothing to do if there is no data for this glyph (and (not (= glyfPos nextPos)) (let () (define port (get-table-stream (glyph-font ttfg) 'glyf)) (pos port (+ (pos port) glyfPos)) (define startPos (pos port)) (define glyph-data (decode GlyfHeader port)) (match (hash-ref glyph-data 'numberOfContours) [(? positive?) (decode-simple glyph-data port)] [(? negative?) (decode-composite glyph-data port startPos)]) glyph-data))) (define (decode-simple glyph-data port) (unless (dict? glyph-data) (raise-argument-error 'TTFGlyph:decode-simple "decoded RGlyfHeader" glyph-data)) (unless (input-port? port) (raise-argument-error 'TTFGlyph:decode-simple "input port" port)) ;; this is a simple glyph (dict-set! glyph-data 'points empty) (define endpts-of-contours (decode (x:array #:type uint16be #:length (hash-ref glyph-data 'numberOfContours)) port)) (dict-set! glyph-data 'instructions (decode (x:array #:type uint8be #:length uint16be) port)) (define num-coords (add1 (last endpts-of-contours))) (define flags (for*/lists (flag-acc) ([i (in-naturals)] #:break (= (length flag-acc) num-coords) [flag (in-value (decode uint8 port))] [count (in-range (add1 (if (not (zero? (bitwise-and flag REPEAT))) (decode uint8 port) 0)))]) flag)) (match-define-values (points _ _) (for/fold ([points empty] [px 0] [py 0]) ([(flag i) (in-indexed flags)]) (define point (+tff-glyph-point (zero? (bitwise-and flag ON_CURVE)) (and (index-of endpts-of-contours i) #t) 0 0)) (define next-px (parse-glyph-coord port px (not (zero? (bitwise-and flag X_SHORT_VECTOR))) (not (zero? (bitwise-and flag SAME_X))))) (define next-py (parse-glyph-coord port py (not (zero? (bitwise-and flag Y_SHORT_VECTOR))) (not (zero? (bitwise-and flag SAME_Y))))) (set-ttf-glyph-point-x! point next-px) (set-ttf-glyph-point-y! point next-py) (values (cons point points) next-px next-py))) (dict-set! glyph-data 'points (reverse points))) (define (decode-composite glyph-data port [offset 0]) ;; this is a composite glyph (dict-set! glyph-data 'components empty) (define haveInstructions #f) (define flags MORE_COMPONENTS) (dict-set! glyph-data 'components (for/list ([i (in-naturals)] #:break (zero? (bitwise-and flags MORE_COMPONENTS))) (set! flags (decode uint16be port)) (define gPos (- (pos port) offset)) (define glyphID (decode uint16be port)) (unless haveInstructions (set! haveInstructions (not (zero? (bitwise-and flags WE_HAVE_INSTRUCTIONS))))) (match-define (list dx dy) (let ([decoder (if (not (zero? (bitwise-and flags ARG_1_AND_2_ARE_WORDS))) int16be int8)]) (list (decode decoder port) (decode decoder port)))) (define component (+ttf-glyph-component glyphID dx dy)) (set-ttf-glyph-component-pos! component gPos) (cond [(not (zero? (bitwise-and flags WE_HAVE_A_SCALE))) (define scale (read-fixed14 port)) (set-ttf-glyph-component-scale-x! component scale) (set-ttf-glyph-component-scale-y! component scale)] [(not (zero? (bitwise-and flags WE_HAVE_AN_X_AND_Y_SCALE))) (set-ttf-glyph-component-scale-x! component (read-fixed14 port)) (set-ttf-glyph-component-scale-y! component (read-fixed14 port))] [(not (zero? (bitwise-and flags WE_HAVE_A_TWO_BY_TWO))) (set-ttf-glyph-component-scale-x! component (read-fixed14 port)) (set-ttf-glyph-component-scale-01! component (read-fixed14 port)) (set-ttf-glyph-component-scale-10! component (read-fixed14 port)) (set-ttf-glyph-component-scale-y! component (read-fixed14 port))]) component)) haveInstructions) (define (bytes->fixed14 b1 b2) (/ (+ (* b1 (expt 2 8)) b2) (expt 2 14) 1.0)) (define (read-fixed14 stream) (define b1 (decode uint8 stream)) (define b2 (decode uint8 stream)) (bytes->fixed14 b1 b2))