main
Matthew Butterick 6 years ago
parent 73df28cfd7
commit 4610e957d6

@ -0,0 +1,5 @@
#lang fontkit/racket
(require "CFF_.rkt")
(provide (rename-out [CFF_ CFF2]))
(test-module)

@ -0,0 +1,18 @@
#lang fontkit/racket
(require xenomorph)
(provide CFF_)
#|
approximates
https://github.com/mbutterick/fontkit/blob/master/src/cff/CFFFont.js
|#
;; the CFFFont object acts as the decoder for the `CFF ` table.
(define-subclass BufferT (RCFF_)
)
(define CFF_ (+RCFF_))
(test-module)

@ -0,0 +1,224 @@
fontkit = require '../pdfkit/node_modules/fontkit'
fira_path = "../pitfall/test/assets/fira.ttf"
f = fontkit.openSync(fira_path)
console.log "*************************** start decode"
thing = f.GPOS.lookupList.get(1)
console.log thing
###
{ version: 65536,
scriptList:
[ { tag: 'DFLT', script: [Object] },
{ tag: 'cyrl', script: [Object] },
{ tag: 'grek', script: [Object] },
{ tag: 'latn', script: [Object] } ],
featureList:
[ { tag: 'cpsp', feature: [Object] },
{ tag: 'cpsp', feature: [Object] },
{ tag: 'cpsp', feature: [Object] },
{ tag: 'cpsp', feature: [Object] },
{ tag: 'cpsp', feature: [Object] },
{ tag: 'cpsp', feature: [Object] },
{ tag: 'cpsp', feature: [Object] },
{ tag: 'cpsp', feature: [Object] },
{ tag: 'cpsp', feature: [Object] },
{ tag: 'cpsp', feature: [Object] },
{ tag: 'cpsp', feature: [Object] },
{ tag: 'cpsp', feature: [Object] },
{ tag: 'cpsp', feature: [Object] },
{ tag: 'cpsp', feature: [Object] },
{ tag: 'kern', feature: [Object] },
{ tag: 'kern', feature: [Object] },
{ tag: 'kern', feature: [Object] },
{ tag: 'kern', feature: [Object] },
{ tag: 'kern', feature: [Object] },
{ tag: 'kern', feature: [Object] },
{ tag: 'kern', feature: [Object] },
{ tag: 'kern', feature: [Object] },
{ tag: 'kern', feature: [Object] },
{ tag: 'kern', feature: [Object] },
{ tag: 'kern', feature: [Object] },
{ tag: 'kern', feature: [Object] },
{ tag: 'kern', feature: [Object] },
{ tag: 'kern', feature: [Object] },
{ tag: 'mark', feature: [Object] },
{ tag: 'mark', feature: [Object] },
{ tag: 'mark', feature: [Object] },
{ tag: 'mark', feature: [Object] },
{ tag: 'mark', feature: [Object] },
{ tag: 'mark', feature: [Object] },
{ tag: 'mark', feature: [Object] },
{ tag: 'mark', feature: [Object] },
{ tag: 'mark', feature: [Object] },
{ tag: 'mark', feature: [Object] },
{ tag: 'mark', feature: [Object] },
{ tag: 'mark', feature: [Object] },
{ tag: 'mark', feature: [Object] },
{ tag: 'mark', feature: [Object] },
{ tag: 'mkmk', feature: [Object] },
{ tag: 'mkmk', feature: [Object] },
{ tag: 'mkmk', feature: [Object] },
{ tag: 'mkmk', feature: [Object] },
{ tag: 'mkmk', feature: [Object] },
{ tag: 'mkmk', feature: [Object] },
{ tag: 'mkmk', feature: [Object] },
{ tag: 'mkmk', feature: [Object] },
{ tag: 'mkmk', feature: [Object] },
{ tag: 'mkmk', feature: [Object] },
{ tag: 'mkmk', feature: [Object] },
{ tag: 'mkmk', feature: [Object] },
{ tag: 'mkmk', feature: [Object] },
{ tag: 'mkmk', feature: [Object] } ],
lookupList:
[ { lookupType: 1,
flags:
{ rightToLeft: false,
ignoreBaseGlyphs: false,
ignoreLigatures: false,
ignoreMarks: false,
useMarkFilteringSet: false,
markAttachmentType: false },
subTableCount: 1,
subTables: [ [Object] ],
markFilteringSet: 2 },
{ lookupType: 2,
flags:
{ rightToLeft: false,
ignoreBaseGlyphs: false,
ignoreLigatures: false,
ignoreMarks: false,
useMarkFilteringSet: false,
markAttachmentType: false },
subTableCount: 6,
subTables: [ [Object], [Object], [Object], [Object], [Object], [Object] ],
markFilteringSet: 2 },
{ lookupType: 2,
flags:
{ rightToLeft: false,
ignoreBaseGlyphs: false,
ignoreLigatures: false,
ignoreMarks: false,
useMarkFilteringSet: false,
markAttachmentType: false },
subTableCount: 7,
subTables:
[ [Object],
[Object],
[Object],
[Object],
[Object],
[Object],
[Object] ],
markFilteringSet: 2 },
{ lookupType: 2,
flags:
{ rightToLeft: false,
ignoreBaseGlyphs: false,
ignoreLigatures: false,
ignoreMarks: false,
useMarkFilteringSet: false,
markAttachmentType: false },
subTableCount: 6,
subTables: [ [Object], [Object], [Object], [Object], [Object], [Object] ],
markFilteringSet: 2 },
{ lookupType: 2,
flags:
{ rightToLeft: false,
ignoreBaseGlyphs: false,
ignoreLigatures: false,
ignoreMarks: false,
useMarkFilteringSet: false,
markAttachmentType: false },
subTableCount: 2,
subTables: [ [Object], [Object] ],
markFilteringSet: 4 },
{ lookupType: 4,
flags:
{ rightToLeft: false,
ignoreBaseGlyphs: false,
ignoreLigatures: false,
ignoreMarks: false,
useMarkFilteringSet: false,
markAttachmentType: false },
subTableCount: 1,
subTables: [ [Object] ],
markFilteringSet: 4 },
{ lookupType: 4,
flags:
{ rightToLeft: false,
ignoreBaseGlyphs: false,
ignoreLigatures: false,
ignoreMarks: false,
useMarkFilteringSet: false,
markAttachmentType: false },
subTableCount: 1,
subTables: [ [Object] ],
markFilteringSet: 4 },
{ lookupType: 4,
flags:
{ rightToLeft: false,
ignoreBaseGlyphs: false,
ignoreLigatures: false,
ignoreMarks: false,
useMarkFilteringSet: false,
markAttachmentType: false },
subTableCount: 1,
subTables: [ [Object] ],
markFilteringSet: 6 },
{ lookupType: 6,
flags:
{ rightToLeft: false,
ignoreBaseGlyphs: false,
ignoreLigatures: false,
ignoreMarks: false,
useMarkFilteringSet: false,
markAttachmentType: false },
subTableCount: 1,
subTables: [ [Object] ],
markFilteringSet: 6 },
{ lookupType: 6,
flags:
{ rightToLeft: false,
ignoreBaseGlyphs: false,
ignoreLigatures: false,
ignoreMarks: false,
useMarkFilteringSet: false,
markAttachmentType: false },
subTableCount: 1,
subTables: [ [Object] ],
markFilteringSet: 6 },
{ lookupType: 6,
flags:
{ rightToLeft: false,
ignoreBaseGlyphs: false,
ignoreLigatures: false,
ignoreMarks: false,
useMarkFilteringSet: false,
markAttachmentType: false },
subTableCount: 1,
subTables: [ [Object] ],
markFilteringSet: 6 },
{ lookupType: 6,
flags:
{ rightToLeft: false,
ignoreBaseGlyphs: false,
ignoreLigatures: false,
ignoreMarks: false,
useMarkFilteringSet: false,
markAttachmentType: false },
subTableCount: 1,
subTables: [ [Object] ],
markFilteringSet: 6 },
{ lookupType: 6,
flags:
{ rightToLeft: false,
ignoreBaseGlyphs: false,
ignoreLigatures: false,
ignoreMarks: false,
useMarkFilteringSet: false,
markAttachmentType: false },
subTableCount: 1,
subTables: [ [Object] ],
markFilteringSet: 1 } ] }
###

@ -0,0 +1,13 @@
#lang fontkit/racket
(require fontkit fontkit/gpos-processor "subset.rkt" rackunit xenomorph racket/serialize)
(define fira-path "../pitfall/test/assets/fira.ttf")
(define f (openSync fira-path))
(define gpos (· f GPOS))
#;(get (· gpos lookupList) 11)
(define gp (+GPOSProcessor f gpos))
(· gpos scriptList)
(send gp selectScript 'cyrl)

@ -0,0 +1,197 @@
#lang fontkit/racket
(require xenomorph br/cond "opentype.rkt")
(provide (all-defined-out))
#|
approximates
https://github.com/mbutterick/fontkit/blob/master/src/tables/GPOS.js
|#
(define ValueFormat (+Bitfield uint16be '(xPlacement yPlacement xAdvance yAdvance xPlaDevice yPlaDevice xAdvDevice yAdvDevice)))
(define types
(mhash
'xPlacement int16be
'yPlacement int16be
'xAdvance int16be
'yAdvance int16be
'xPlaDevice (+Pointer uint16be Device (mhash 'type 'global 'relativeTo 'rel))
'yPlaDevice (+Pointer uint16be Device (mhash 'type 'global 'relativeTo 'rel))
'xAdvDevice (+Pointer uint16be Device (mhash 'type 'global 'relativeTo 'rel))
'yAdvDevice (+Pointer uint16be Device (mhash 'type 'global 'relativeTo 'rel))))
(define-subclass object% (ValueRecord [key 'valueFormat])
(define/public (buildStruct parent)
;; set `struct` to the first dict in the chain of ancestors
;; with the target key
(define struct (let loop ([x parent])
(cond
[(and x (dict? x) (dict-ref x key #f)) x]
[(· x parent) => loop]
[else #f])))
(and struct
(let ()
(define format (dict-ref struct key))
(define fields
(append
(dictify 'rel (λ _ (dict-ref struct '_startOffset)))
(for/list ([(key val) (in-dict format)]
#:when val)
(cons key (dict-ref types key)))))
(+Struct fields))))
(define/public (size val ctx)
(send (buildStruct ctx) size val ctx))
(define/public (decode port parent)
(define res (send (buildStruct parent) decode port parent))
(dict-remove! res 'rel)
res)
(define/public (encode . args)
(error 'GPOS-encode-not-implemented)))
(define PairValueRecord (+Struct
(dictify
'secondGlyph uint16be
'value1 (+ValueRecord 'valueFormat1)
'value2 (+ValueRecord 'valueFormat2))))
(define PairSet (+Array PairValueRecord uint16be))
(define Class2Record (+Struct
(dictify
'value1 (+ValueRecord 'valueFormat1)
'value2 (+ValueRecord 'valueFormat2))))
(define Anchor (+VersionedStruct uint16be
(dictify
;; Design units only
1 (dictify 'xCoordinate int16be
'yCoordinate int16be)
;; Design units plus contour point
2 (dictify 'xCoordinate int16be
'yCoordinate int16be
'anchorPoint uint16be)
;; Design units plus Device tables
3 (dictify 'xCoordinate int16be
'yCoordinate int16be
'xDeviceTable (+Pointer uint16be Device)
'yDeviceTable (+Pointer uint16be Device)))))
(define EntryExitRecord (+Struct
(dictify 'entryAnchor (+Pointer uint16be Anchor (mhash 'type 'parent))
'exitAnchor (+Pointer uint16be Anchor (mhash 'type 'parent)))))
(define MarkRecord (+Struct
(dictify 'class uint16be
'markAnchor uint16be)))
(define MarkArray (+Array MarkRecord uint16be))
(define BaseRecord (+Array (+Pointer uint16be Anchor) (λ (t) (ref* t 'parent 'classCount))))
(define BaseArray (+Array BaseRecord uint16be))
(define ComponentRecord (+Array (+Pointer uint16be Anchor) (λ (t) (ref* t 'parent 'parent 'classCount))))
(define LigatureAttach (+Array ComponentRecord uint16be))
(define LigatureArray (+Array (+Pointer uint16be LigatureAttach) uint16be))
(define-subclass VersionedStruct (GPOSLookup-VersionedStruct))
(define GPOSLookup
(+GPOSLookup-VersionedStruct
'lookupType
(dictify
;; Single Adjustment
1 (+VersionedStruct uint16be
(dictify
;; Single positioning value
1 (dictify
'coverage (+Pointer uint16be Coverage)
'valueFormat ValueFormat
'value (+ValueRecord))
2 (dictify
'coverage (+Pointer uint16be Coverage)
'valueFormat ValueFormat
'valueCount uint16be
'values (+LazyArray (+ValueRecord) 'valueCount))))
;; Pair Adjustment Positioning
2 (+VersionedStruct uint16be
(dictify
;; Adjustments for glyph pairs
1 (dictify
'coverage (+Pointer uint16be Coverage)
'valueFormat1 ValueFormat
'valueFormat2 ValueFormat
'pairSetCount uint16be
'pairSets (+LazyArray (+Pointer uint16be PairSet) 'pairSetCount))
;; Class pair adjustment
2 (dictify
'coverage (+Pointer uint16be Coverage)
'valueFormat1 ValueFormat
'valueFormat2 ValueFormat
'classDef1 (+Pointer uint16be ClassDef)
'classDef2 (+Pointer uint16be ClassDef)
'class1Count uint16be
'class2Count uint16be
'classRecords (+LazyArray (+LazyArray Class2Record 'class2Count) 'class1Count))))
;; Cursive Attachment Positioning
3 (dictify
'format uint16be
'coverage (+Pointer uint16be Coverage)
'entryExitCount uint16be
'entryExitRecords (+Array EntryExitRecord 'entryExitCount))
;; MarkToBase Attachment Positioning
4 (dictify
'format uint16be
'markCoverage (+Pointer uint16be Coverage)
'baseCoverage (+Pointer uint16be Coverage)
'classCount uint16be
'markArray (+Pointer uint16be MarkArray)
'baseArray (+Pointer uint16be BaseArray))
;; MarkToLigature Attachment Positioning
5 (dictify
'format uint16be
'markCoverage (+Pointer uint16be Coverage)
'ligatureCoverage (+Pointer uint16be Coverage)
'classCount uint16be
'markArray (+Pointer uint16be MarkArray)
'ligatureArray (+Pointer uint16be LigatureArray))
;; MarkToMark Attachment Positioning
6 (dictify
'format uint16be
'mark1Coverage (+Pointer uint16be Coverage)
'mark2Coverage (+Pointer uint16be Coverage)
'classCount uint16be
'mark1Array (+Pointer uint16be MarkArray)
'mark2Array (+Pointer uint16be BaseArray))
7 Context ;; Contextual positioning
8 ChainingContext ;; Chaining contextual positioning
;; Extension positioning
9 (dictify
'posFormat uint16be
'lookupType uint16be ;; cannot also be 9
'extension (+Pointer uint32be (λ () (error 'circular-reference-unfixed))))
)))
;; Fix circular reference
(ref*-set! GPOSLookup 'versions 9 'extension 'type GPOSLookup)
(define-subclass VersionedStruct (GPOS-MainVersionedStruct))
(define GPOS (+GPOS-MainVersionedStruct uint32be
(dictify
'header (dictify 'scriptList (+Pointer uint16be ScriptList)
'featureList (+Pointer uint16be FeatureList)
'lookupList (+Pointer uint16be (LookupList GPOSLookup)))
#x00010000 (dictify)
#;#x00010001 #;(+Pointer uint32be FeatureVariations))))
(test-module)

@ -0,0 +1,6 @@
fontkit = require '../pdfkit/node_modules/fontkit'
fira_path = "../pitfall/test/assets/fira.ttf"
f = fontkit.openSync(fira_path)
console.log f.GSUB.lookupList.get(30).subTables
console.log f.GSUB.lookupList.get(30).subTables[0].ligatureSets.get(0)

@ -0,0 +1,89 @@
#lang fontkit/racket
(require xenomorph br/cond "opentype.rkt")
(provide (all-defined-out))
#|
approximates
https://github.com/mbutterick/fontkit/blob/master/src/tables/GSUB.js
|#
(define Sequence (+Array uint16be uint16be))
(define AlternateSet Sequence)
(define Ligature (+Struct
(dictify
'glyph uint16be
'compCount uint16be
'components (+Array uint16be (λ (t) (sub1 (· t compCount)))))))
(define LigatureSet (+Array (+Pointer uint16be Ligature) uint16be))
(define-subclass VersionedStruct (GSUBLookup-VersionedStruct))
(define GSUBLookup
(+GSUBLookup-VersionedStruct
'lookupType
(dictify
;; Single Substitution
1 (+VersionedStruct uint16be
(dictify
1 (dictify
'coverage (+Pointer uint16be Coverage)
'deltaGlyphID int16be)
2 (dictify
'coverage (+Pointer uint16be Coverage)
'glyphCount uint16be
'substitute (+LazyArray uint16be 'glyphCount))))
2 ;; Multiple Substitution
(dictify
'substFormat uint16be
'coverage (+Pointer uint16be Coverage)
'count uint16be
'sequences (+LazyArray (+Pointer uint16be Sequence) 'count))
3 ;; Alternate Substitution
(dictify
'substFormat uint16be
'coverage (+Pointer uint16be Coverage)
'count uint16be
'alternateSet (+LazyArray (+Pointer uint16be AlternateSet) 'count))
4 ;; Ligature Substitution
(dictify
'substFormat uint16be
'coverage (+Pointer uint16be Coverage)
'count uint16be
'ligatureSets (+LazyArray (+Pointer uint16be LigatureSet) 'count))
5 Context ;; Contextual Substitution
6 ChainingContext ;; Chaining Contextual Substitution
7 ;; Extension Substitution
(dictify
'substFormat uint16be
'lookupType uint16be ; cannot also be 7
'extension (+Pointer uint32be (λ () (error 'circular-reference-unfixed))))
8 ;; Reverse Chaining Contextual Single Substitution
(dictify
'substFormat uint16be
'coverage (+Pointer uint16be Coverage)
'backTrackCoverage (+Array (+Pointer uint16be Coverage) 'backtrackGlyphCount)
'lookaheadGlyphCount uint16be
'lookaheadCoverage (+Array (+Pointer uint16be Coverage) 'lookaheadGlyphCount)
'glyphCount uint16be
'substitute (+Array uint16be 'glyphCount)))))
;; Fix circular reference
(ref*-set! GSUBLookup 'versions 7 'extension 'type GSUBLookup)
(define-subclass VersionedStruct (GSUB-MainVersionedStruct))
(define GSUB (+GSUB-MainVersionedStruct uint32be
(dictify
'header (dictify 'scriptList (+Pointer uint16be ScriptList)
'featureList (+Pointer uint16be FeatureList)
'lookupList (+Pointer uint16be (LookupList GSUBLookup))
)
#x00010000 (dictify)
#;#x00010001 #;(+Pointer uint32be FeatureVariations))))
(test-module)

@ -0,0 +1,78 @@
#lang fontkit/racket
(require xenomorph)
(provide (all-defined-out))
#|
approximates
https://github.com/mbutterick/fontkit/blob/master/src/tables/OS2.js
|#
(define-subclass VersionedStruct (ROS/2))
(define OS/2 (let ()
(define type-1
(dictify 'typoAscender int16be
'typoDescender int16be
'typoLineGap int16be
'winAscent uint16be
'winDescent uint16be
'codePageRange (+Array uint32be 2)))
(define type-2
(dictify 'xHeight int16be
'capHeight int16be
'defaultChar uint16be
'breakChar uint16be
'maxContent uint16be))
(define type-5
(dictify 'usLowerOpticalPointSize uint16be
'usUpperOpticalPointSize uint16be))
(+ROS/2
uint16be
(dictify
'header (dictify 'xAvgCharWidth int16be ;; average weighted advance width of lower case letters and space
'usWeightClass uint16be ;; visual weight of stroke in glyphs
'usWidthClass uint16be ;; relative change from the normal aspect ratio (width to height ratio)
;; Indicates font embedding licensing rights
'fsType (+Bitfield uint16be '(null noEmbedding viewOnly editable null null null null noSubsetting bitmapOnly))
'ySubscriptXSize int16be ;; recommended horizontal size in pixels for subscripts
'ySubscriptYSize int16be ;; recommended vertical size in pixels for subscripts
'ySubscriptXOffset int16be ;; recommended horizontal offset for subscripts
'ySubscriptYOffset int16be ;; recommended vertical offset form the baseline for subscripts
'ySuperscriptXSize int16be ;; recommended horizontal size in pixels for superscripts
'ySuperscriptYSize int16be ;; recommended vertical size in pixels for superscripts
'ySuperscriptXOffset int16be ;; recommended horizontal offset for superscripts
'ySuperscriptYOffset int16be ;; recommended vertical offset from the baseline for superscripts
'yStrikeoutSize int16be ;; width of the strikeout stroke
'yStrikeoutPosition int16be ;; position of the strikeout stroke relative to the baseline
'sFamilyClass int16be ;; classification of font-family design
'panose (+Array uint8 10) ;; describe the visual characteristics of a given typeface
'ulCharRange (+Array uint32be 4)
'vendorID (+Symbol 4) ;; four character identifier for the font vendor
;; bit field containing information about the font
'fsSelection (+Bitfield uint16 '(italic underscore negative outlined strikeout bold regular useTypoMetrics wws oblique))
'usFirstCharIndex uint16be ;; The minimum Unicode index in this font
'usLastCharIndex uint16be) ;; The maximum Unicode index in this font
0 null
1 type-1
2 (append type-1 type-2)
3 (append type-1 type-2)
4 (append type-1 type-2)
5 (append type-1 type-2 type-5)))))
(test-module
(define ip (open-input-file charter-path))
(define dir (deserialize (read (open-input-file charter-directory-path))))
(define offset (· dir tables OS/2 offset))
(define len (· dir tables OS/2 length))
(check-equal? offset 360)
(check-equal? len 96)
(define ds (open-input-bytes (peek-bytes len offset ip)))
(define version (send uint16be decode ds))
(send OS/2 force-version! version)
(define table-data (send OS/2 decode ds))
(check-equal? (· table-data panose) '(2 0 5 3 6 0 0 2 0 4))
(check-equal? (· table-data sFamilyClass) 0))

@ -0,0 +1,160 @@
#lang fontkit/racket
(require "ot-processor.rkt" "glyphinfo.rkt" br/cond)
(provide (all-defined-out))
#|
approximates
https://github.com/mbutterick/fontkit/blob/master/src/opentype/GSUBProcessor.js
|#
(define-subclass OTProcessor (GSUBProcessor)
(define/override (applyLookup lookupType table)
(report/file 'GSUBProcessor:applyLookup)
(case lookupType
[(1) ;; Single Substitution
(report 'single-substitution)
(define index (send this coverageIndex (· table coverage)))
(cond
[(= index -1) #f]
[else (define glyph (· this glyphIterator cur))
(set-field! id glyph
(case (· table version)
[(1) (bitwise-and (+ (· glyph id) (· table deltaGlyphID)) #xffff)]
[(2) (send (· table substitute) get index)]))
#t])]
[(2) ;; Multiple Substitution
(report 'multiple-substitution)
(define index (send this coverageIndex (· table coverage)))
(cond
[(= index -1) #f]
[else (define sequence (send (· table sequences) get index))
(set-field! id (· this glyphIterator cur) (list-ref sequence 0))
(set-field! ligatureComponent (· this glyphIterator cur) 0)
(define features (· this glyphIterator cur features))
(define curGlyph (· this glyphIterator cur))
(define replacement (for/list ([(gid i) (in-indexed (cdr sequence))])
(define glyph (+GlyphInfo (· this font) gid #f features))
(set-field! shaperInfo glyph (· curGlyph shaperInfo))
(set-field! isLigated glyph (· curGlyph isLigated))
(set-field! ligatureComponent glyph (add1 i))
(set-field! substituted glyph #t)
glyph))
(set-field! glyphs this (let-values ([(head tail) (split-at (· this glyphs) (add1 (· this glyphIterator index)))])
(append head replacement tail)))
#t])]
[(3) ;; Alternate substitution
(report 'altnernate-substitution)
(define index (send this coverageIndex (· table coverage)))
(cond
[(= index -1) #f]
[else (define USER_INDEX 0)
(set-field! id (· this glyphIterator cur) (list-ref (send (· table alternateSet) get index) USER_INDEX))
#t])]
[(4) ;; Ligature substitution
(report 'ligature-substitution)
(define index (report* (· table coverage) (send this coverageIndex (· table coverage))))
(cond
[(= index -1) #f]
[(for* ([ligature (in-list (send (· table ligatureSets) get index))]
[matched (in-value (send this sequenceMatchIndices 1 (report* ligature (· ligature components))))]
#:when matched)
(report*/file matched (· this glyphs) index)
(define curGlyph (· this glyphIterator cur))
;; Concatenate all of the characters the new ligature will represent
(define characters
(append (· curGlyph codePoints)
(append* (for/list ([index (in-list matched)])
(get-field codePoints (list-ref (· this glyphs) index))))))
;; Create the replacement ligature glyph
(define ligatureGlyph (+GlyphInfo (· this font) (· ligature glyph) characters (· curGlyph features)))
(set-field! shaperInfo ligatureGlyph (· curGlyph shaperInfo))
(set-field! ligated ligatureGlyph #t)
(set-field! substituted ligatureGlyph #t)
(report 'from-harfbuzz)
;; From Harfbuzz:
;; - If it *is* a mark ligature, we don't allocate a new ligature id, and leave
;; the ligature to keep its old ligature id. This will allow it to attach to
;; a base ligature in GPOS. Eg. if the sequence is: LAM,LAM,SHADDA,FATHA,HEH,
;; and LAM,LAM,HEH for a ligature, they will leave SHADDA and FATHA with a
;; ligature id and component value of 2. Then if SHADDA,FATHA form a ligature
;; later, we don't want them to lose their ligature id/component, otherwise
;; GPOS will fail to correctly position the mark ligature on top of the
;; LAM,LAM,HEH ligature. See https://bugzilla.gnome.org/show_bug.cgi?id=676343
;;
;; - If a ligature is formed of components that some of which are also ligatures
;; themselves, and those ligature components had marks attached to *their*
;; components, we have to attach the marks to the new ligature component
;; positions! Now *that*'s tricky! And these marks may be following the
;; last component of the whole sequence, so we should loop forward looking
;; for them and update them.
;;
;; Eg. the sequence is LAM,LAM,SHADDA,FATHA,HEH, and the font first forms a
;; 'calt' ligature of LAM,HEH, leaving the SHADDA and FATHA with a ligature
;; id and component == 1. Now, during 'liga', the LAM and the LAM-HEH ligature
;; form a LAM-LAM-HEH ligature. We need to reassign the SHADDA and FATHA to
;; the new ligature with a component value of 2.
;;
;; This in fact happened to a font... See
;; https://bugzilla.gnome.org/show_bug.cgi?id=437633
(define isMarkLigature
(and (· curGlyph isMark)
(for/and ([match-idx (in-list matched)])
(· (list-ref (· this glyphs) match-idx) isMark))))
(report isMarkLigature)
(set-field! ligatureID ligatureGlyph (cond
[isMarkLigature #f]
[else (define id (· this ligatureID))
(increment-field! ligatureID this)
id]))
(define lastLigID (· curGlyph ligatureID))
(define lastNumComps (length (· curGlyph codePoints)))
(define curComps lastNumComps)
(define idx (add1 (· this glyphIterator index)))
(report/file 'set-ligature-id)
;; Set ligatureID and ligatureComponent on glyphs that were skipped in the matched sequence.
;; This allows GPOS to attach marks to the correct ligature components.
(for ([matchIndex (in-list matched)])
(report/file matchIndex)
;; Don't assign new ligature components for mark ligatures (see above)
(cond
[isMarkLigature (set! idx matchIndex)]
[else (while (< idx matchIndex)
(define ligatureComponent (+ curComps (- lastNumComps) (min (or (get-field ligatureComponent (list-ref (· this glyphs) idx)) 1) lastNumComps)))
(set-field! ligatureID (list-ref (· this glyphs) idx) (· ligatureGlyph ligatureID))
(set-field! ligatureComponent (list-ref (· this glyphs) idx) ligatureComponent)
(increment! idx))])
(define lastLigID (· (list-ref (· this glyphs) idx) ligatureID))
(define lastNumComps (length (· (list-ref (· this glyphs) idx) codePoints)))
(increment! curComps lastNumComps)
(increment! idx)) ;; skip base glyph
;; Adjust ligature components for any marks following
(when (and lastLigID (not isMarkLigature))
(for ([i (in-range idx (length (· this glyphs)))]
#:when (= (· (list-ref (· this glyphs) idx) ligatureID) lastLigID))
(define ligatureComponent (+ curComps (- lastNumComps) (min (or (get-field ligatureComponent (list-ref (· this glyphs) i)) 1) lastNumComps)))
(set-field! ligatureComponent (list-ref (· this glyphs) i) ligatureComponent)))
;; Delete the matched glyphs, and replace the current glyph with the ligature glyph
(set-field! glyphs this (drop-right (· this glyphs) (length matched)))
(set-field! glyphs this (list-set (· this glyphs) (· this glyphIterator index) ligatureGlyph))
#t)]
[else #f])])))

@ -0,0 +1,50 @@
#lang fontkit/racket
(provide BBox bbox->list)
(define-subclass object% (BBox
; The minimum X position in the bounding box
[minX +inf.0]
; The minimum Y position in the bounding box
[minY +inf.0]
; The maxmimum X position in the bounding box
[maxX -inf.0]
; The maxmimum Y position in the bounding box
[maxY -inf.0])
(as-methods
width
height
addPoint
copy))
;; The width of the bounding box
(define/contract (width this)
(->m number?)
(- (· this maxX) (· this minX)))
;; The height of the bounding box
(define/contract (height this)
(->m number?)
(- (· this maxY) (· this minY)))
(define/contract (addPoint this x y)
(number? number? . ->m . void?)
(set-field! minX this (min x (· this minX)))
(set-field! minY this (min y (· this minY)))
(set-field! maxX this (max x (· this maxX)))
(set-field! maxY this (max y (· this maxY))))
(define/contract (copy this)
(->m (is-a?/c BBox))
(make-object BBox (· this minX)
(· this minY)
(· this maxX)
(· this maxY)))
(define/contract (bbox->list this)
((is-a?/c BBox) . -> . (list/c number? number? number? number?))
(list (· this minX) (· this minY) (· this maxX) (· this maxY)))

@ -0,0 +1,32 @@
((3)
0
()
0
()
()
(h
!
()
(tag u . "\u0000\u0001\u0000\u0000")
(rangeShift . 96)
(searchRange . 128)
(numTables . 14)
(tables
h
!
(equal)
(loca h ! () (tag u . "loca") (offset . 38692) (checkSum . 2795817194) (length . 460))
(OS/2 h ! () (tag u . "OS/2") (offset . 360) (checkSum . 2351070438) (length . 96))
(glyf h ! () (tag u . "glyf") (offset . 4620) (checkSum . 1143629849) (length . 34072))
(hhea h ! () (tag u . "hhea") (offset . 292) (checkSum . 132056097) (length . 36))
(post h ! () (tag u . "post") (offset . 41520) (checkSum . 1670855689) (length . 514))
(cvt_ h ! () (tag u . "cvt ") (offset . 4592) (checkSum . 10290865) (length . 26))
(VDMX h ! () (tag u . "VDMX") (offset . 1372) (checkSum . 1887795202) (length . 1504))
(prep h ! () (tag u . "prep") (offset . 4512) (checkSum . 490862356) (length . 78))
(maxp h ! () (tag u . "maxp") (offset . 328) (checkSum . 50135594) (length . 32))
(hmtx h ! () (tag u . "hmtx") (offset . 456) (checkSum . 3982043058) (length . 916))
(cmap h ! () (tag u . "cmap") (offset . 2876) (checkSum . 1723761408) (length . 1262))
(name h ! () (tag u . "name") (offset . 39152) (checkSum . 2629707307) (length . 2367))
(head h ! () (tag u . "head") (offset . 236) (checkSum . 4281190895) (length . 54))
(fpgm h ! () (tag u . "fpgm") (offset . 4140) (checkSum . 106535991) (length . 371)))
(entrySelector . 3)))

@ -0,0 +1,32 @@
((3)
0
()
0
()
()
(h
!
()
(tag u . "\u0000\u0001\u0000\u0000")
(rangeShift . 96)
(searchRange . 128)
(numTables . 14)
(tables
h
!
(equal)
(loca h ! () (tag u . "loca") (offset . 37392) (checkSum . 46801904) (length . 460))
(OS/2 h ! () (tag u . "OS/2") (offset . 360) (checkSum . 2367847603) (length . 96))
(glyf h ! () (tag u . "glyf") (offset . 4620) (checkSum . 2099535230) (length . 32772))
(hhea h ! () (tag u . "hhea") (offset . 292) (checkSum . 113838023) (length . 36))
(post h ! () (tag u . "post") (offset . 40280) (checkSum . 1671576585) (length . 514))
(cvt_ h ! () (tag u . "cvt ") (offset . 4592) (checkSum . 9307818) (length . 26))
(VDMX h ! () (tag u . "VDMX") (offset . 1372) (checkSum . 1905948947) (length . 1504))
(prep h ! () (tag u . "prep") (offset . 4512) (checkSum . 776081685) (length . 78))
(maxp h ! () (tag u . "maxp") (offset . 328) (checkSum . 50135583) (length . 32))
(hmtx h ! () (tag u . "hmtx") (offset . 456) (checkSum . 3798537071) (length . 916))
(cmap h ! () (tag u . "cmap") (offset . 2876) (checkSum . 1723761408) (length . 1262))
(name h ! () (tag u . "name") (offset . 37852) (checkSum . 2313429994) (length . 2427))
(head h ! () (tag u . "head") (offset . 236) (checkSum . 4275817075) (length . 54))
(fpgm h ! () (tag u . "fpgm") (offset . 4140) (checkSum . 106535991) (length . 371)))
(entrySelector . 3)))

@ -0,0 +1,7 @@
#lang fontkit/racket
(require racket/serialize)
(provide cloneDeep)
(define (cloneDeep val)
(deserialize (serialize val)))

@ -0,0 +1,5 @@
#lang fontkit/racket
(provide CmapProcessor)
(define-subclass object% (CmapProcessor cmapTable)
(super-new))

@ -0,0 +1,29 @@
#lang fontkit/racket
(require xenomorph)
(provide (all-defined-out))
#|
approximates
https://github.com/mbutterick/fontkit/blob/master/src/tables/cvt.js
|#
(define-subclass Struct (Rcvt_))
(define cvt_ (+Rcvt_
(dictify
'controlValues (+Array int16be))))
(test-module
(define ip (open-input-file charter-path))
(define dir (deserialize (read (open-input-file charter-directory-path))))
(define offset (· dir tables cvt_ offset))
(define len (· dir tables cvt_ length))
(check-equal? offset 4592)
(check-equal? len 26)
(set-port-position! ip 0)
(define table-bytes #"\0\24\0+\0S\0\0\0\20\377&\0\0\1\341\0\v\2\237\0\22\2\340\0\b")
(check-equal? table-bytes (peek-bytes len offset ip))
(define ds (open-input-bytes (peek-bytes len offset ip)))
(define cvt-array '(20 43 83 0 16 -218 0 481 11 671 18 736 8))
(check-equal? (dict-ref (decode cvt_ ds) 'controlValues) cvt-array)
(check-equal? (encode cvt_ (mhash 'controlValues cvt-array) #f) table-bytes))

@ -0,0 +1,47 @@
#lang fontkit/racket
(provide (all-defined-out))
(define VARIATION_FEATURES '(rvrn))
(define COMMON_FEATURES '(ccmp locl rlig mark mkmk))
(define FRACTIONAL_FEATURES '(frac numr dnom))
(define HORIZONTAL_FEATURES '(calt clig liga rclt curs kern))
(define VERTICAL_FEATURES '(vert))
(define DIRECTIONAL_FEATURES (mhasheq
'ltr '(ltra ltrm)
'rtl '(rtla rtlm)))
(define zeroMarkWidths 'AFTER_GPOS)
(define-subclass object% (DefaultShaper)
(define/public (plan plan_ glyphs features)
#;(report*/file plan_ glyphs features)
;; Plan the features we want to apply
(planPreprocessing plan_)
(planFeatures plan_)
(planPostprocessing plan_ features)
;; Assign the global features to all the glyphs
(send plan_ assignGlobalFeatures glyphs)
;; Assign local features to glyphs
(assignFeatures plan_ glyphs))
(define/public (planPreprocessing plan)
(send plan add (mhasheq
'global (append VARIATION_FEATURES (dict-ref DIRECTIONAL_FEATURES (· plan direction)))
'local FRACTIONAL_FEATURES)))
(define/public (planFeatures plan)
;; Do nothing by default. Let subclasses override this.
(void))
(define/public (planPostprocessing plan userFeatures)
(when userFeatures
(unless (and (list? userFeatures) (andmap symbol? userFeatures))
(raise-argument-error 'DefaultShaper:planPostprocessing "list of features" userFeatures)))
(send plan add (append COMMON_FEATURES HORIZONTAL_FEATURES (or userFeatures empty))))
(define/public (assignFeatures plan glyphs)
;; todo: Enable contextual fractions
(void)))

@ -0,0 +1,68 @@
#lang fontkit/racket
(require xenomorph "tables.rkt" describe)
(provide (all-defined-out))
#|
https://github.com/mbutterick/fontkit/blob/master/src/tables/directory.js
|#
(define TableEntry (+Struct
(dictify 'tag (+Symbol 4)
'checkSum uint32be
'offset (+Pointer uint32be 'void (mhash 'type 'global))
'length uint32be)))
;; for stupid tags like 'cvt '
(define (symbol-replace sym this that)
(string->symbol (string-replace (if (string? sym) sym (symbol->string sym)) this that)))
(define (escape-tag tag) (symbol-replace tag " " "_"))
(define (unescape-tag tag) (symbol-replace tag "_" " "))
(define-subclass Struct (RDirectory)
(define/augride (post-decode this-res stream ctx)
(define new-tables-val (mhash))
(for ([table (in-list (· this-res tables))])
(hash-set! new-tables-val (escape-tag (· table tag)) table))
(dict-set! this-res 'tables new-tables-val)
this-res)
(define/augride (pre-encode this-val port)
(define tables (for/list ([(tag table) (in-hash (· this-val tables))])
(define table-codec (hash-ref table-codecs tag))
(mhash 'tag (unescape-tag tag)
'checkSum 0
'offset (+VoidPointer table-codec table)
'length (send table-codec size table))))
(define numTables (length tables))
(define searchRange (* (floor (log numTables 2)) 16))
(hash-set*! this-val
'tag 'true
'numTables numTables
'tables tables
'searchRange searchRange
'entrySelector (floor (/ searchRange (log 2)))
'rangeShift (- (* numTables 16) searchRange))
this-val))
(define Directory (+RDirectory (dictify 'tag (+Symbol 4)
'numTables uint16be
'searchRange uint16be
'entrySelector uint16be
'rangeShift uint16be
'tables (+Array TableEntry 'numTables))))
(define (directory-decode ip [options (mhash)])
(send Directory decode ip))
(define (file-directory-decode ps)
(directory-decode (open-input-file ps)))
#;(test-module
(define ip (open-input-file charter-path))
(define decoded-dir (deserialize (read (open-input-file charter-directory-path))))
(check-equal? (directory-decode ip) decoded-dir))

@ -0,0 +1,327 @@
#lang debug fontkit/racket
(require "freetype-ffi.rkt" (except-in ffi/unsafe array?) racket/runtime-path "subset.rkt" "glyph.rkt" "layout-engine.rkt" "bbox.rkt" "glyphrun.rkt" "cmap-processor.rkt" "directory.rkt" xenomorph "tables.rkt" "ttfglyph.rkt")
(provide (all-defined-out))
#|
approximates
https://github.com/mbutterick/fontkit/blob/master/src/TTFFont.js
|#
(require (for-syntax "tables.rkt"))
(define-macro (define-table-getters)
(with-pattern ([(TABLE-TAG ...) (hash-keys table-codecs)])
#'(begin
(define/public (TABLE-TAG) (_getTable 'TABLE-TAG)) ...)))
(test-module
(define f (openSync (path->string charter-path)))
(define otf (openSync (path->string fira-otf-path)))
(check-equal? (postscriptName f) "Charter"))
;; This is the base class for all SFNT-based font formats in fontkit.
;; (including CFF)
;; It supports TrueType, and PostScript glyphs, and several color glyph formats.
(define-subclass object% (TTFFont port [_src #f])
(when port (unless (input-port? port)
(raise-argument-error 'TTFFont "input port" port)))
(unless (member (peek-bytes 4 0 port) (list #"true" #"OTTO" (bytes 0 1 0 0)))
(raise 'probe-fail))
;; skip variationCoords
(field [_directoryPos (pos port)]
[_tables (mhash)] ; holds decoded tables (loaded lazily)
[_glyphs (mhash)]
[_layoutEngine #f])
(field [directory #f])
(send this _decodeDirectory)
(define/public (_getTable table-tag)
(unless (has-table? this table-tag)
(raise-argument-error '_getTable "table that exists in font" table-tag))
(dict-ref! _tables table-tag (_decodeTable table-tag))) ; get table from cache, load if not there
(define-table-getters)
(define/public (_getTableStream tag)
(define table (dict-ref (· this directory tables) tag))
(cond
[table
(pos port (· table offset))
port]
[else #f]))
(define/public (_decodeTable table-tag)
(define table-decoder (hash-ref table-codecs table-tag
(λ () (raise-argument-error '_decodeTable "decodable table" table-tag))))
(define offset (· (hash-ref (· directory tables) table-tag) offset))
(define len (· (hash-ref (· directory tables) table-tag) length))
(pos port 0)
(decode table-decoder (open-input-bytes (peek-bytes len offset port)) #:parent this))
(define/public (_decodeDirectory)
(set! directory (decode Directory port #:parent (mhash '_startOffset 0)))
directory)
(field [ft-library (FT_Init_FreeType)]
[ft-face (and _src (FT_New_Face ft-library _src 0))])
(as-methods
postscriptName
measure-string
unitsPerEm
ascent
descent
lineGap
underlinePosition
underlineThickness
italicAngle
capHeight
xHeight
bbox
createSubset
has-table?
has-cff-table?
has-morx-table?
has-gsub-table?
has-gpos-table?
getGlyph
layout
glyphsForString
glyphForCodePoint))
;; The unique PostScript name for this font
(define/contract (postscriptName this)
(->m string?)
(FT_Get_Postscript_Name (· this ft-face)))
;; The size of the fonts internal coordinate grid
(define/contract (unitsPerEm this)
(->m number?)
(· this head unitsPerEm))
(test-module
(check-equal? (· f unitsPerEm) 1000))
;; The fonts [ascender](https://en.wikipedia.org/wiki/Ascender_(typography))
(define/contract (ascent this)
(->m number?)
(· this hhea ascent))
(test-module
(check-equal? (· f ascent) 980))
;; The fonts [descender](https://en.wikipedia.org/wiki/Descender)
(define/contract (descent this)
(->m number?)
(· this hhea descent))
(test-module
(check-equal? (· f descent) -238))
;; The amount of space that should be included between lines
(define/contract (lineGap this)
(->m number?)
(· this hhea lineGap))
(test-module
(check-equal? (· f lineGap) 0))
(define/contract (underlinePosition this)
(->m number?)
(· this post underlinePosition))
(test-module
(check-equal? (· f underlinePosition) -178))
(define/contract (underlineThickness this)
(->m number?)
(· this post underlineThickness))
(test-module
(check-equal? (· f underlineThickness) 58))
;; If this is an italic font, the angle the cursor should be drawn at to match the font design
(define/contract (italicAngle this)
(->m number?)
(· this post italicAngle))
(test-module
(check-equal? (· f italicAngle) 0))
;; The height of capital letters above the baseline.
(define/contract (capHeight this)
(->m number?)
(if (send this has-table? #"OS/2")
(· this OS/2 capHeight)
(· this ascent)))
(test-module
(check-equal? (· f capHeight) 671))
;; The height of lower case letters in the font.
(define/contract (xHeight this)
(->m number?)
(if (send this has-table? #"OS/2")
(· this OS/2 xHeight)
0))
(test-module
(check-equal? (· f xHeight) 481))
;; The fonts bounding box, i.e. the box that encloses all glyphs in the font.
(define/contract (bbox this)
(->m (is-a?/c BBox))
(make-object BBox (· this head xMin)
(· this head yMin)
(· this head xMax)
(· this head yMax)))
(test-module
(check-equal? (bbox->list (· f bbox)) '(-161 -236 1193 963)))
(define/contract (_cmapProcessor this)
(->m (is-a?/c CmapProcessor))
(make-object CmapProcessor (· this cmap)))
;; Returns a Subset for this font.
(define/contract (createSubset this)
(->m (is-a?/c Subset))
(make-object (if (· this has-cff-table?)
CFFSubset
TTFSubset) this))
(define/contract (has-table? this tag)
((or/c bytes? symbol?) . ->m . boolean?)
(dict-has-key? (· this directory tables) (if (bytes? tag)
(string->symbol (bytes->string/latin-1 tag))
tag)))
(define has-cff-table? (curryr has-table? 'CFF_))
(define has-morx-table? (curryr has-table? 'morx))
(define has-gpos-table? (curryr has-table? 'GPOS))
(define has-gsub-table? (curryr has-table? 'GSUB))
(test-module
(check-false (· f has-cff-table?))
(check-false (· f has-morx-table?))
(check-false (· f has-gsub-table?))
(check-false (· f has-gpos-table?)))
;; Returns a glyph object for the given glyph id.
;; You can pass the array of code points this glyph represents for
;; your use later, and it will be stored in the glyph object.
(define/contract (getGlyph this glyph [characters null])
((index?) ((listof index?)) . ->*m . (is-a?/c Glyph))
(make-object (if (· this has-cff-table?)
CFFGlyph
TTFGlyph) glyph characters this))
;; Returns a GlyphRun object, which includes an array of Glyphs and GlyphPositions for the given string.
(define/contract (layout this string [userFeatures #f] [script #f] [language #f])
((string?) ((option/c (listof symbol?)) (option/c symbol?) (option/c symbol?)) . ->*m . GlyphRun?)
(unless (· this _layoutEngine)
(set-field! _layoutEngine this (+LayoutEngine this)))
#;(report*/file 'in-layout (· this _layoutEngine))
(send (· this _layoutEngine) layout string userFeatures script language))
;; Returns an array of Glyph objects for the given string.
;; This is only a one-to-one mapping from characters to glyphs.
;; For most uses, you should use font.layout (described below), which
;; provides a much more advanced mapping supporting AAT and OpenType shaping.
(define/contract (glyphsForString this string)
(string? . ->m . (listof (is-a?/c Glyph)))
;; todo: make this handle UTF-16 with surrogate bytes
;; for now, just use UTF-8
(define codepoints (map char->integer (string->list string)))
(for/list ([cp (in-list codepoints)])
(send this glyphForCodePoint cp)))
;; Maps a single unicode code point to a Glyph object.
;; Does not perform any advanced substitutions (there is no context to do so).
(define/contract (glyphForCodePoint this codePoint)
(index? . ->m . Glyph?)
(define glyph-idx (FT_Get_Char_Index (· this ft-face) codePoint))
(send this getGlyph glyph-idx (list codePoint)))
(define/contract (measure-char-width this char)
(char? . ->m . number?)
(define glyph-idx (FT_Get_Char_Index (· this ft-face) (char->integer char)))
(FT_Load_Glyph (· this ft-face) glyph-idx FT_LOAD_NO_RECURSE)
(define width (FT_Vector-x (FT_GlyphSlotRec-advance (FT_FaceRec-glyph (· this ft-face)))))
(* width 1.0))
(define/contract (measure-string this str size)
(string? number? . ->m . number?)
(/ (* size
(for/sum ([c (in-string str)])
(measure-char-width this c))) (· this unitsPerEm)))
#|
approximates
https://github.com/mbutterick/fontkit/blob/master/src/index.js
|#
;; Register font formats
(define formats (list TTFFont))
;;fontkit.registerFormat(WOFFFont); ;; todo
;;fontkit.registerFormat(WOFF2Font); ;; todo
;;fontkit.registerFormat(TrueTypeCollection); ;; todo
;;fontkit.registerFormat(DFont); ;; todo
#|
approximates
https://github.com/mbutterick/fontkit/blob/master/src/base.js
|#
(define/contract (openSync str-or-path [postscriptName #f])
(((or/c path? string?)) ((option/c string?)) . ->* . TTFFont?)
(define filename (if (path? str-or-path) (path->string str-or-path) str-or-path))
(define buffer (file->bytes filename))
(create buffer filename postscriptName))
(define/contract (create buffer [filename #f] [postscriptName #f])
((bytes?) ((option/c path-string?) (option/c string?)) . ->* . TTFFont?)
(or
(for*/first ([format (in-list formats)]
;; rather than use a `probe` function,
;; just try making a font with each format and see what happens
[font (in-value (with-handlers ([(curry eq? 'probe-fail) (λ (exn) #f)])
(make-object format (open-input-bytes buffer) filename)))]
#:when font)
(if postscriptName
(send font getFont postscriptName) ; used to select from collection files like TTC
font))
(error 'fontkit:create "unknown font format")))
(test-module
(check-equal? (measure-string f "f" (· f unitsPerEm)) 321.0)
(check-true (send f has-table? #"cmap"))
(check-exn exn:fail:contract? (λ () (send f _getTable 'nonexistent-table-tag))))

@ -0,0 +1,30 @@
#lang fontkit/racket
(require xenomorph)
(provide (all-defined-out))
#|
approximates
https://github.com/mbutterick/fontkit/blob/master/src/tables/fpgm.js
|#
;; A list of instructions that are executed once when a font is first used.
;; These instructions are known as the font program. The main use of this table
;; is for the definition of functions that are used in many different glyph programs.
(define-subclass Struct (fpgm%))
(define fpgm (make-object fpgm%
(dictify
'instructions (make-object Array uint8))))
(test-module
(define ip (open-input-file charter-path))
(define dir (deserialize (read (open-input-file charter-directory-path))))
(define offset (· dir tables fpgm offset))
(define len (· dir tables fpgm length))
(check-equal? offset 4140)
(check-equal? len 371)
(check-equal? (pos ip 0) 0)
(check-equal? (dict-ref (send fpgm decode (peek-bytes len offset ip)) 'instructions) '(184 0 0 44 75 184 0 9 80 88 177 1 1 142 89 184 1 255 133 184 0 68 29 185 0 9 0 3 95 94 45 184 0 1 44 32 32 69 105 68 176 1 96 45 184 0 2 44 184 0 1 42 33 45 184 0 3 44 32 70 176 3 37 70 82 88 35 89 32 138 32 138 73 100 138 32 70 32 104 97 100 176 4 37 70 32 104 97 100 82 88 35 101 138 89 47 32 176 0 83 88 105 32 176 0 84 88 33 176 64 89 27 105 32 176 0 84 88 33 176 64 101 89 89 58 45 184 0 4 44 32 70 176 4 37 70 82 88 35 138 89 32 70 32 106 97 100 176 4 37 70 32 106 97 100 82 88 35 138 89 47 253 45 184 0 5 44 75 32 176 3 38 80 88 81 88 176 128 68 27 176 64 68 89 27 33 33 32 69 176 192 80 88 176 192 68 27 33 89 89 45 184 0 6 44 32 32 69 105 68 176 1 96 32 32 69 125 105 24 68 176 1 96 45 184 0 7 44 184 0 6 42 45 184 0 8 44 75 32 176 3 38 83 88 176 64 27 176 0 89 138 138 32 176 3 38 83 88 35 33 176 128 138 138 27 138 35 89 32 176 3 38 83 88 35 33 184 0 192 138 138 27 138 35 89 32 176 3 38 83 88 35 33 184 1 0 138 138 27 138 35 89 32 176 3 38 83 88 35 33 184 1 64 138 138 27 138 35 89 32 184 0 3 38 83 88 176 3 37 69 184 1 128 80 88 35 33 184 1 128 35 33 27 176 3 37 69 35 33 35 33 89 27 33 89 68 45 184 0 9 44 75 83 88 69 68 27 33 33 89 45)))

@ -0,0 +1,427 @@
#lang racket/base
(require ffi/unsafe
ffi/unsafe/define
racket/draw/private/libs)
(define-syntax-rule (define+provide id val)
(begin
(define id val)
(provide id)))
(define-runtime-lib freetype-lib
[(unix) (ffi-lib "libfontconfig" '("1" ""))]
[(macosx) (ffi-lib "libfreetype.6.dylib")]
[(windows) (ffi-lib "libfreetype-6.dll")])
(define-ffi-definer define-freetype freetype-lib #:provide provide)
;; types
(define _void-pointer (_cpointer 'void-pointer))
(define _char _byte)
(define _char-pointer (_cpointer 'char-pointer))
(define _uchar _ubyte)
(define _FT_Byte _ubyte)
(define _FT_Bytes _bytes)
(define _FT_Char _char)
(define _FT_Int _int)
(define _FT_UInt _uint)
(define _FT_Int16 _short)
(define _FT_UInt16 _ushort)
(define _FT_Int32 _int32)
(define _FT_UInt32 _uint32)
(define _FT_Short _short)
(define _FT_UShort _ushort)
(define _FT_Long _long)
(define _FT_ULong _ulong)
(define _FT_Bool _byte)
(define _FT_Offset _size) ;; equivalent to _size_t?
(define _FT_PtrDist _ptrdiff) ;; equivalent to _longlong?
(define _FT_String _char)
(define _FT_String-pointer (_cpointer 'FT_String-pointer)) ;; char*
(define _FT_Tag _FT_UInt32)
(define _FT_Error _int)
(define _FT_Fixed _long)
(define _FT_Pointer _void-pointer)
(define _FT_Pos _long)
(define _FT_FWord _short)
(define _FT_UFWord _ushort)
(define _FT_F26Dot16 _short)
(define _FT_F26Dot6 _long)
(define _FT_Glyph_Format _int)
(define _FT_Encoding _int)
(define _FT_Generic_Finalizer (_cpointer '_FT_Generic_Finalizer (_fun _void-pointer -> _void)))
(define _FT_LibraryRec (_cpointer 'FT_LibraryRec))
(define _FT_Library (_cpointer 'FT_Library))
(define-cstruct _FT_Bitmap_Size
([height _FT_Short]
[width _FT_Short]
[size _FT_Pos]
[x_ppem _FT_Pos]
[y_ppem _FT_Pos]))
(define-cstruct _FT_CharMapRec
([face _void-pointer] ; should be FT_Face
[encoding _FT_Encoding]
[platform_id _FT_UShort]
[encoding_id _FT_UShort]))
(define _FT_CharMap _FT_CharMapRec-pointer)
(define _FT_CharMap-pointer (_cpointer 'FT_CharMap-pointer))
(define-cstruct _FT_Generic
([data _void-pointer]
[finalizer _FT_Generic_Finalizer]))
(define-cstruct _FT_BBox
([xMin _FT_Pos]
[yMin _FT_Pos]
[xMax _FT_Pos]
[yMax _FT_Pos]))
(provide (struct-out FT_BBox)
_FT_BBox _FT_BBox-pointer)
(define-cstruct _FT_Glyph_Metrics
([width _FT_Pos]
[height _FT_Pos]
[horiBearingX _FT_Pos]
[horiBearingY _FT_Pos]
[horiAdvance _FT_Pos]
[vertBearingX _FT_Pos]
[vertBearingY _FT_Pos]
[vertAdvance _FT_Pos]))
(provide (struct-out FT_Glyph_Metrics)
_FT_Glyph_Metrics _FT_Glyph_Metrics-pointer)
(define-cstruct _FT_Vector
([x _FT_Pos]
[y _FT_Pos]))
(provide (struct-out FT_Vector)
_FT_Vector _FT_Vector-pointer)
(define-cstruct _FT_Bitmap
([rows _int]
[width _int]
[pitch _int]
[buffer (_cpointer 'buffer)]
[num_grays _short]
[pixel_mode _ubyte]
[palette_mode _char]
[palette _void-pointer]))
(define-cstruct _FT_Outline
([n_contours _short]
[n_points _short]
[points _FT_Vector-pointer]
[tags (_cpointer 'tags)]
[contours (_cpointer 'contours)]
[flags _int]))
(define-cstruct _FT_GlyphSlotRec
([library _FT_Library]
[face _void-pointer]
[next _void-pointer]
[reserved _uint]
[generic _FT_Generic]
[metrics _FT_Glyph_Metrics]
[linearHoriAdvance _FT_Fixed]
[linearVertAdvance _FT_Fixed]
[advance _FT_Vector]
[format _FT_Glyph_Format]
[bitmap _FT_Bitmap]
[bitmap_left _FT_Int]
[bitmap_top _FT_Int]
[outline _FT_Outline]
[num_subglyphs _FT_UInt]
[subglyphs _void-pointer]
[control_data _void-pointer]
[control_len _long]
[lsb_delta _FT_Pos]
[rsb_delta _FT_Pos]
[other _void-pointer]
[internal _void-pointer]))
(define _FT_GlyphSlot _FT_GlyphSlotRec-pointer)
(provide (struct-out FT_GlyphSlotRec)
_FT_GlyphSlotRec _FT_GlyphSlotRec-pointer)
(define-cstruct _FT_Size_Metrics
([x_ppem _FT_UShort]
[y_ppem _FT_UShort]
[x_scale _FT_Fixed]
[y_scale _FT_Fixed]
[ascender _FT_Pos]
[descender _FT_Pos]
[height _FT_Pos]
[max_advance _FT_Pos]))
(define-cstruct _FT_SizeRec
([face _void-pointer]
[generic _FT_Generic]
[metrics _FT_Size_Metrics]
[internal _void-pointer]))
(define _FT_Size _FT_SizeRec-pointer)
(define-cstruct _FT_FaceRec
([num_faces _FT_Long]
[face_index _FT_Long]
[face_flag _FT_Long]
[style_flags _FT_Long]
[num_glyphs _FT_Long]
[family_name _string] ; probably _string is a better choice
[style_name _string]
[num_fixed_sizes _FT_Int]
[available_sizes _FT_Bitmap_Size-pointer]
[num_charmaps _FT_Int]
[charmaps _FT_CharMap-pointer]
[generic _FT_Generic]
[bbox _FT_BBox]
[units_per_EM _FT_UShort]
[ascender _FT_Short]
[descender _FT_Short]
[height _FT_Short]
[max_advance_width _FT_Short]
[max_advance_height _FT_Short]
[underline_position _FT_Short]
[underline_thickness _FT_Short]
[glyph _FT_GlyphSlot]
[size _FT_Size]
[charmap _FT_CharMap]
[driver _void-pointer]
[memory _void-pointer]
[stream _void-pointer]
[sizes_list_head _void-pointer]
[sizes_list_tail _void-pointer]
[autohint _FT_Generic]
[extensions _void-pointer]
[internal _void-pointer]))
(define _FT_Face _FT_FaceRec-pointer)
(provide (struct-out FT_FaceRec)
_FT_FaceRec _FT_FaceRec-pointer)
(define _FT_Sfnt_Tag _FT_ULong)
(define-cstruct _FT_HoriHeader
([version _FT_Long]
[ascent _FT_Short]
[descent _FT_Short]
[lineGap _FT_Short]))
(provide (struct-out FT_HoriHeader)
_FT_HoriHeader _FT_HoriHeader-pointer)
(define-cstruct _FT_TT_Postscript
([FormatType _FT_Fixed]
[italicAngle _FT_Fixed]
[underlinePosition _FT_Short]
[underlineThickness _FT_Short]
[isFixedPitch _FT_ULong]
[minMemType42 _FT_ULong]
[maxMemType42 _FT_ULong]
[minMemType1 _FT_ULong]
[maxMemType1 _FT_ULong]))
(provide (struct-out FT_TT_Postscript)
_FT_TT_Postscript _FT_TT_Postscript-pointer)
(define-cstruct _FT_panose
([a _FT_Byte]
[b _FT_Byte]
[c _FT_Byte]
[d _FT_Byte]
[e _FT_Byte]
[f _FT_Byte]
[g _FT_Byte]
[h _FT_Byte]
[i _FT_Byte]
[j _FT_Byte]))
(define-cstruct _FT_VendID
([a _FT_Char]
[b _FT_Char]
[c _FT_Char]
[d _FT_Char]))
(define-cstruct _FT_TT_OS2
([version _FT_UShort]
[xAvgCharWidth _FT_Short]
[usWeightClass _FT_UShort]
[usWidthClass _FT_UShort]
[fsType _FT_Short]
[ySubscriptXSize _FT_Short]
[ySubscriptYSize _FT_Short]
[ySubscriptXOffset _FT_Short]
[ySubscriptYOffset _FT_Short]
[ySuperscriptXSize _FT_Short]
[ySuperscriptYSize _FT_Short]
[ySuperscriptXOffset _FT_Short]
[ySuperscriptYOffset _FT_Short]
[yStrikeoutSize _FT_Short]
[yStrikeoutPosition _FT_Short]
[sFamilyClass _FT_Short]
[panose _FT_panose]
[ulUnicodeRange1 _FT_ULong]
[ulUnicodeRange2 _FT_ULong]
[ulUnicodeRange3 _FT_ULong]
[ulUnicodeRange4 _FT_ULong]
[achVendID _FT_VendID]
[fsSelection _FT_UShort]
[usFirstCharIndex _FT_UShort]
[usLastCharIndex _FT_UShort]
[sTypoAscender _FT_Short]
[sTypoDescender _FT_Short]
[sTypoLineGap _FT_Short]
[usWinAscent _FT_UShort]
[usWinDescent _FT_UShort]
[ulCodePageRange1 _FT_ULong]
[ulCodePageRange2 _FT_ULong]
[sxHeight _FT_Short]
[sCapHeight _FT_Short]
[usDefaultChar _FT_UShort]
[usBreakChar _FT_UShort]
[usMaxContext _FT_UShort]
[usLowerOpticalPointSize _FT_UShort]
[usUpperOpticalPointSize _FT_UShort]))
(provide (struct-out FT_TT_OS2)
_FT_TT_OS2 _FT_TT_OS2-pointer)
(define _full-path
(make-ctype _path
path->complete-path
values))
(define-freetype FT_Init_FreeType (_fun (ftl : (_ptr o _FT_Library))
-> (err : _FT_Error)
-> (if (zero? err) ftl (error 'FT_Init_FreeType))))
(define-freetype FT_New_Face (_fun _FT_Library _full-path _FT_Long
(ftf : (_ptr o (_or-null _FT_Face)))
-> (err : _FT_Error)
-> (if (zero? err) ftf (error 'FT_New_Face (format "error ~a" err)))))
(define-freetype FT_Done_Face (_fun _FT_Face
-> (err : _FT_Error)
-> (unless (zero? err) (error 'FT_Done_Face (format "error ~a" err)))))
(define-freetype FT_Done_FreeType (_fun _FT_Library -> (err : _FT_Error) -> (if (zero? err) (void) (error 'FT_Done_FreeType))))
(define-freetype FT_Get_Kerning (_fun _FT_Face _FT_UInt _FT_UInt _FT_UInt
(ftv : (_ptr o _FT_Vector))
-> (err : _FT_Error)
-> (if (zero? err) ftv (error 'FT_Get_Kerning (format "error ~a" err)))))
(define-freetype FT_Get_Char_Index (_fun _FT_Face _FT_ULong
-> _FT_UInt))
(define-freetype FT_Load_Glyph (_fun _FT_Face _FT_UInt _FT_Int32
-> (err : _FT_Error)
-> (unless (zero? err)
(error 'FT_Load_Glyph "failed, try using FT_LOAD_NO_RECURSE flag instead"))))
(define-freetype FT_Load_Char (_fun _FT_Face _FT_ULong _FT_Int32
-> (err : _FT_Error)))
(define+provide FT_KERNING_UNSCALED 2)
(define+provide FT_LOAD_DEFAULT 0)
(define+provide FT_LOAD_RENDER (expt 2 2))
(define+provide FT_LOAD_LINEAR_DESIGN (expt 2 13))
(define+provide FT_LOAD_NO_RECURSE (expt 2 10))
(define-freetype FT_Get_Postscript_Name (_fun _FT_Face -> _string))
(define-freetype FT_Load_Sfnt_Table (_fun _FT_Face _FT_Sfnt_Tag _FT_Long
(buffer : (_ptr io _FT_Byte))
(len : (_ptr io _FT_ULong))
-> (err : _FT_Error)
-> (and (zero? err) #t)))
(define+provide _FT_Gettable_Sfnt_Tag (_enum '(ft_sfnt_head = 0
ft_sfnt_maxp
ft_sfnt_os2
ft_sfnt_hhea
ft_sfnt_vhea
ft_sfnt_post
ft_sfnt_pclt)))
(define-freetype FT_Get_Sfnt_Table (_fun _FT_Face _FT_Gettable_Sfnt_Tag
-> (p : (_cpointer/null 'table-ptr))
-> (or p (error 'sfnt-table-not-loaded))))
(define-freetype FT_Select_Charmap (_fun _FT_Face _FT_Encoding
-> (err : _FT_Error)
-> (unless (zero? err) (error 'FT_Select_Charmap-failed))))
(define-freetype FT_Set_Charmap (_fun _FT_Face _FT_CharMapRec
-> (err : _FT_Error)
-> (unless (zero? err) (error 'FT_Set_Charmap-failed))))
(provide tag->int)
(define (tag->int tag)
(define signed? #f)
(define big-endian? #t)
(integer-bytes->integer tag signed? big-endian?))
(define (int->tag int)
(define signed? #f)
(define big-endian? #t)
(integer->integer-bytes int 4 signed? big-endian?))
(module+ test
(require rackunit)
(define ft-library (FT_Init_FreeType))
(define face (FT_New_Face ft-library "../pitfall/test/assets/Charter.ttf" 0))
(check-equal? (FT_Get_Postscript_Name face) "Charter")
(check-equal? (FT_FaceRec-units_per_EM face) 1000)
(check-true (FT_Load_Sfnt_Table face (tag->int #"cmap") 0 0 0))
(check-false (FT_Load_Sfnt_Table face (tag->int #"zzap") 0 0 0))
(check-true (cpointer? (FT_Get_Sfnt_Table face 'ft_sfnt_hhea)))
(define charter-hhea-table (cast (FT_Get_Sfnt_Table face 'ft_sfnt_hhea) _pointer _FT_HoriHeader-pointer))
(check-equal? (FT_HoriHeader-ascent charter-hhea-table) 980)
(check-equal? (FT_HoriHeader-descent charter-hhea-table) -238)
(check-equal? (FT_HoriHeader-lineGap charter-hhea-table) 0)
(check-equal?
(let ([bbox (FT_FaceRec-bbox face)])
(list (FT_BBox-xMin bbox)
(FT_BBox-yMin bbox)
(FT_BBox-xMax bbox)
(FT_BBox-yMax bbox))) '(-161 -236 1193 963))
(define H-gid (FT_Get_Char_Index face 72))
(FT_Load_Glyph face H-gid FT_LOAD_NO_RECURSE)
; want bearingX (lsb) and advanceX (advance width)
(define g (FT_FaceRec-glyph face))
(define metrics (FT_GlyphSlotRec-metrics g))
(define bearingX (FT_Glyph_Metrics-horiBearingX metrics))
(check-equal? bearingX 33)
(define advanceX (FT_Glyph_Metrics-horiAdvance metrics))
(check-equal? advanceX 738)
(define charter-post-table (cast (FT_Get_Sfnt_Table face 'ft_sfnt_post) _pointer _FT_TT_Postscript-pointer))
(check-equal? (FT_TT_Postscript-italicAngle charter-post-table) 0)
(check-equal? (FT_TT_Postscript-underlinePosition charter-post-table) -178) ; -207 + 1/2 of thickness = -207 + 29
(check-equal? (FT_TT_Postscript-underlineThickness charter-post-table) 58)
(define os2-table (cast (FT_Get_Sfnt_Table face 'ft_sfnt_os2) _pointer _FT_TT_OS2-pointer))
(check-equal? (FT_TT_OS2-fsType os2-table) #b1000)
(check-equal? (FT_TT_OS2-yStrikeoutSize os2-table) 61)
(check-equal? (FT_TT_OS2-yStrikeoutPosition os2-table) 240)
(check-equal? (FT_panose->list (FT_TT_OS2-panose os2-table)) '(2 0 5 3 6 0 0 2 0 4))
(check-equal? (FT_TT_OS2-sTypoAscender os2-table) 762)
(check-equal? (FT_TT_OS2-sTypoDescender os2-table) -238)
(check-equal? (FT_TT_OS2-sCapHeight os2-table) 671)
(check-equal? (FT_TT_OS2-sxHeight os2-table) 481)
(FT_Done_Face face)
)

@ -0,0 +1,21 @@
#lang fontkit/racket
(require xenomorph)
(provide (all-defined-out))
#|
approximates
https://github.com/mbutterick/fontkit/blob/master/src/tables/glyf.js
|#
(define-subclass Struct (Rglyf))
(define glyf (+Array (+BufferT)))
(test-module
(define ip (open-input-file charter-path))
(define dir (deserialize (read (open-input-file charter-directory-path))))
(define offset (· dir tables glyf offset))
(define len (· dir tables glyf length))
(check-equal? offset 4620)
(check-equal? len 34072)
(set-port-position! ip 0)
(define table-bytes (peek-bytes len offset ip)))

@ -0,0 +1,79 @@
#lang fontkit/racket
(require br/cond)
(provide (all-defined-out))
#|
approximates
https://github.com/mbutterick/fontkit/blob/master/src/opentype/GlyphIterator.js
|#
(define-subclass object%
(GlyphIterator glyphs [flags (mhasheq)])
(field [index #f])
(reset flags)
(define/public (reset flags)
(set-field! flags this flags)
(set-field! index this 0))
(define/public (cur)
(and (< (· this index) (length (· this glyphs)))
(list-ref (· this glyphs) (· this index))))
(define/public (shouldIgnore glyph flags)
(or (and (· flags ignoreMarks) (· glyph isMark))
(and (· flags ignoreBaseGlyphs) (not (· glyph isMark)))
(and (· flags ignoreLigatures) (· glyph isLigature))))
(define/public (move dir)
(unless (= (abs dir) 1)
(raise-argument-error 'GlyphIterator:move "1 or -1" dir))
(increment-field! index this dir)
(while (and (<= 0 (· this index))
(< (· this index) (length (· this glyphs)))
(send this shouldIgnore (list-ref (· this glyphs) (· this index)) (· this flags)))
(increment-field! index this dir))
(if (or (> 0 (· this index))
(>= (· this index) (length (· this glyphs))))
#f
(list-ref (· this glyphs) (· this index))))
(define/public (next) (move 1))
(define/public (prev) (move -1))
(define/public (peek [count 1])
(define idx (· this index))
(define res (send this increment count))
(set-field! index this idx)
res)
(define/public (peekIndex [count 1])
(define idx (· this index))
(send this increment count)
(define res (· this index))
(set-field! index this idx)
res)
(define/public (increment [count 1])
(for/last ([i (in-range (abs count))])
(send this move (if (negative? count) -1 1)))))
(test-module
(define gi (+GlyphIterator '(a b c)))
(check-equal? (· gi index) 0)
(check-equal? (send gi cur) 'a)
(check-equal? (send gi move 1) 'b)
(check-equal? (send gi move 1) 'c)
(check-false (send gi move 1))
(check-false (send gi cur))
(check-equal? (send gi increment -3) 'a)
(check-equal? (send gi cur) 'a)
(check-equal? (send gi peek 1) 'b)
(check-equal? (send gi peek 2) 'c)
(check-equal? (send gi peek 3) #f)
(check-equal? (send gi cur) 'a)
)

@ -0,0 +1,29 @@
#lang fontkit/racket
(provide (all-defined-out))
;; Represents positioning information for a glyph in a GlyphRun.
(define-subclass object% (GlyphPosition
;; The amount to move the virtual pen in the X direction after rendering this glyph.
[xAdvance 0]
;; The amount to move the virtual pen in the Y direction after rendering this glyph.
[yAdvance 0]
;; The offset from the pen position in the X direction at which to render this glyph.
[xOffset 0]
;; The offset from the pen position in the Y direction at which to render this glyph.
[yOffset 0]
[advanceWidth 0])
(as-methods
scale)
)
(define/contract (scale this factor)
(number? . ->m . (is-a?/c GlyphPosition))
(set-field! xAdvance this (* factor (· this xAdvance)))
(set-field! yAdvance this (* factor (· this yAdvance)))
(set-field! xOffset this (* factor (· this xOffset)))
(set-field! yOffset this (* factor (· this yOffset)))
(set-field! advanceWidth this (* factor (· this advanceWidth)))
this)

@ -0,0 +1,80 @@
#lang fontkit/racket
(require "freetype-ffi.rkt")
(provide (all-defined-out))
(module+ test (require rackunit))
#|
/**
* Glyph objects represent a glyph in the font. They have various properties for accessing metrics and
* the actual vector path the glyph represents, and methods for rendering the glyph to a graphics context.
*
* You do not create glyph objects directly. They are created by various methods on the font object.
* There are several subclasses of the base Glyph class internally that may be returned depending
* on the font format, but they all inherit from this class.
*/
|#
#|
approximates
https://github.com/mbutterick/fontkit/blob/master/src/glyph/Glyph.js
|#
(define-subclass object% (Glyph id codePoints font)
(field [_font font]
[isMark (andmap is-mark? codePoints)]
[isLigature (> (length codePoints) 1)]
[_metrics #f])
(as-methods
_getPath
_getCBox
_getBBox
_getTableMetrics
advanceWidth
_getMetrics))
(define-stub-stop _getPath)
(define-stub-stop _getCBox)
(define-stub-stop _getBBox)
(define-stub-stop _getTableMetrics)
(define/contract (advanceWidth this)
(->m number?)
(hash-ref (_getMetrics this) 'advanceWidth))
(define/contract (_getMetrics this)
(->m hash?)
(unless (· this _metrics)
(define face (· this _font ft-face))
(FT_Load_Glyph face (· this id) FT_LOAD_NO_RECURSE)
(define glyph (FT_FaceRec-glyph face))
(define glyph-metrics (FT_GlyphSlotRec-metrics glyph))
(set-field! _metrics this (mhash))
(hash-set*! (· this _metrics)
'advanceWidth (FT_Glyph_Metrics-horiAdvance glyph-metrics)
'leftBearing (FT_Glyph_Metrics-horiBearingX glyph-metrics)))
(· this _metrics))
#|
approximates
https://github.com/mbutterick/fontkit/blob/master/src/glyph/CFFGlyph.js
|#
(define-subclass Glyph (CFFGlyph)
(error 'cff-glyph-unimplemented)
#;(define/override (_getName this)
(->m any/c)
(if (send (· this _font) _getTable 'CFF2)
(super _getName)
(send (send (· this _font) _getTable 'CFF_) getGlyphName (· this id))))
(as-methods
#;_getName
#;bias
#;_getPath))

@ -0,0 +1,49 @@
#lang fontkit/racket
(require "ot-processor.rkt")
(provide (all-defined-out))
#|
approximates
https://github.com/mbutterick/fontkit/blob/master/src/opentype/GlyphInfo.js
|#
(define-subclass object% (GlyphInfo font-in id-in [codePoints-in empty] [features-in (mhasheq)])
(field [_font font-in]
[codePoints codePoints-in]
[_id id-in]
[features (mhasheq)])
(cond
[(list? features-in)
(for ([feature (in-list features-in)])
(hash-set! features feature #t))]
[(dict? features-in)
(for ([(feature val) (in-dict features-in)])
(hash-set! features feature val))])
(field [ligatureID #f]
[ligatureComponent #f]
[ligated #f]
[isLigated #f] ;todo: is this deliberate or accidental? see gsub-processor
[cursiveAttachment #f]
[markattachment #f]
[shaperInfo #f]
[substituted #f]
[isMark #f]
[isLigature #f])
(define/public (id [id-in #f])
(cond
[(not id-in) _id]
[else (set-field! _id this id-in)
(set-field! substituted this #t)
(cond
[(and (· this _font GDEF) (· this _font GDEF glyphClassDef))
(define classID (send (+OTProcessor) getClassID id-in (· this _font GDEF glyphClassDef)))
(set-field! isMark this (= classID 3))
(set-field! isLigature this (= classID 2))]
[else
(set-field! isMark this (andmap is-mark? (· this codePoints)))
(set-field! isLigature this (> (length (· this codePoints)) 1))])])))

@ -0,0 +1,19 @@
#lang fontkit/racket
(require "bbox.rkt" (prefix-in Script- "script.rkt"))
(provide (all-defined-out))
#|
approximates
https://github.com/mbutterick/fontkit/blob/master/src/layout/GlyphRun.js
|#
;; Represents a run of Glyph and GlyphPosition objects.
;; Returned by the font layout method.
(define-subclass object% (GlyphRun
glyphs ; An array of Glyph objects in the run
positions) ; An array of GlyphPosition objects for each glyph in the run
(define/public (advanceWidth)
(for/sum ([pos (in-list positions)])
(· pos xAdvance))))

@ -0,0 +1,31 @@
#lang fontkit/racket
(require fontkit "gpos-processor.rkt" rackunit xenomorph racket/serialize describe)
(define fira-path "../pitfall/test/assets/fira.ttf")
(define f (openSync fira-path))
(define gpos (· f GPOS))
(define proc (+GPOSProcessor f gpos))
(check-equal? (dump (· proc features))
'((cpsp (lookupCount . 1) (lookupListIndexes 0) (featureParams . 0))
(mkmk (lookupCount . 5) (lookupListIndexes 8 9 10 11 12) (featureParams . 0))
(mark (lookupCount . 3) (lookupListIndexes 5 6 7) (featureParams . 0))
(kern (lookupCount . 4) (lookupListIndexes 1 2 3 4) (featureParams . 0))))
(check-equal? (dump (· proc script))
'((count . 0)
(defaultLangSys (featureIndexes 0 14 28 42)
(reserved . 0)
(reqFeatureIndex . 65535)
(featureCount . 4))
(langSysRecords)))
(check-equal? (dump (· proc scriptTag)) 'DFLT)
(check-equal? (dump (· proc language))
'((featureIndexes 0 14 28 42)
(reserved . 0)
(reqFeatureIndex . 65535)
(featureCount . 4)))
(check-equal? (dump (· proc languageTag)) #f)
(check-equal? (dump (· proc lookups)) empty)
(check-equal? (dump (· proc direction)) 'ltr)

@ -0,0 +1,103 @@
#lang fontkit/racket
(require "ot-processor.rkt")
(provide (all-defined-out))
#|
https://github.com/mbutterick/fontkit/blob/master/src/opentype/GPOSProcessor.js
|#
(define-subclass OTProcessor (GPOSProcessor)
(define/public (applyPositionValue sequenceIndex value)
(define position (list-ref (· this positions) (send (· this glyphIterator) peekIndex sequenceIndex)))
(when (· value xAdvance)
(increment-field! xAdvance position (or (· value xAdvance) 0)))
(when (· value yAdvance)
(increment-field! yAdvance position (· value yAdvance)))
(when (· value xPlacement)
(increment-field! xOffset position (· value xPlacement)))
(when (· value yPlacement)
(increment-field! yOffset position (· value yPlacement))))
(define/override (applyLookup lookupType table)
(case lookupType
[(1) ;; Single positioning value
#;(report/file 'single-positioning-value)
(define index (send this coverageIndex (· table coverage)))
#;(report/file index)
(cond
[(= index -1) #f]
[else #;(report (· table version))
(case (· table version)
[(1) (send this applyPositionValue 0 (· table value))]
[(2) (send this applyPositionValue 0 (send (· table values) get index))])
#t])]
[(2) ;; Pair Adjustment Positioning
#;(report/file 'applyLookup:pair-adjustment)
(define nextGlyph (· this glyphIterator peek))
(cond
[(not nextGlyph) #f]
[else
#;(report 'getting-pair-coverage-for)
#;(report* (· this glyphIterator cur id) (· this glyphIterator peek id) (· table coverage))
(define index (send this coverageIndex (· table coverage)))
#;(report index)
(cond
[(= index -1) #f]
[else
#;(report (· table version))
(case (· table version)
[(1) ;; Adjustments for glyph pairs
#;(report 'glyph-pair)
(define set (send (· table pairSets) get index))
(for/first ([pair (in-list set)]
#:when (= (· pair secondGlyph) (· nextGlyph id)))
(send this applyPositionValue 0 (· pair value1))
(send this applyPositionValue 0 (· pair value2)))]
[(2) ;; Class pair adjustment
#;(report/file 'class-pair)
(define class1 (send this getClassID (· this glyphIterator cur id) (· table classDef1)))
(define class2 (send this getClassID (· nextGlyph id) (· table classDef2)))
(cond
[(or (= class1 -1) (= class2 -1)) #f]
[else (define pair (send (send (· table classRecords) get class1) get class2))
(send this applyPositionValue 0 (· pair value1))
(send this applyPositionValue 0 (· pair value2))
#t])])])])]
[(3) ;; Cursive Attachment Positioning
#;(report/file 'cursive-attachment-positioning-unimplemented)
(void)]
[(4) ;; Mark to base positioning
#;(report/file 'mark-to-base-positioning-unimplemented)
(void)]
[(5) ;; Mark to ligature positioning
#;(report/file 'mark-to-ligature-positioning-unimplemented)
(void)]
[(6) ;; Mark to mark positioning
#;(report/file 'mark-to-mark-positioning-unimplemented)
(void)]
[(7) ;; Contextual positioning
#;(report/file 'contextual-positioning-unimplemented)
(void)]
[(8) ;; Chaining contextual positioning
#;(report/file 'chaining-contextual-positioning-unimplemented)
(void)]
[(9) ;; Extension positioning
#;(report/file 'extension-contextual-positioning-unimplemented)
(void)]
[else
(raise-argument-error 'GPOSProcessor:applyLookup "supported GPOS table" lookupType)]))
(define/override (applyFeatures userFeatures glyphs advances)
(super applyFeatures userFeatures glyphs advances)
#;(report/file 'fixCursiveAttachment-unimplemented)
#;(for ([i (in-range (length (· this glyphs)))])
(send this fixCursiveAttachment i))
#;(report/file 'fixMarkAttachment-unimplemented)
#;(send this fixMarkAttachment))
)

@ -0,0 +1,24 @@
#lang fontkit/racket
(require fontkit "gsub-processor.rkt" rackunit xenomorph racket/serialize describe)
(define fira-path "../pitfall/test/assets/fira.ttf")
(define f (openSync fira-path))
(define gsub (· f GSUB))
(define proc (+GSUBProcessor f gsub))
(check-equal? (map car (dump (· proc features)))
'(c2sc pnum liga tnum onum ss01 dlig lnum sups zero ss02 aalt subs ss03 ordn calt dnom smcp salt case numr frac mgrk))
(check-equal? (dict-ref (dump (· proc language)) 'featureIndexes)
'(0 14 28 42 56 70 84 98 112 136 150 164 178 192 206 220 234 248 262 276 290 304 318))
(check-equal? (dump (· proc scriptTag)) 'DFLT)
(check-equal? (dict-ref (dump (· proc language)) 'featureIndexes)
'(0 14 28 42 56 70 84 98 112 136 150 164 178 192 206 220 234 248 262 276 290 304 318))
(check-equal? (dump (· proc languageTag)) #f)
(check-equal? (dump (· proc lookups)) empty)
(check-equal? (dump (· proc direction)) 'ltr)

@ -0,0 +1,148 @@
#lang fontkit/racket
(require "ot-processor.rkt" "glyphinfo.rkt" br/cond)
(provide (all-defined-out))
#|
approximates
https://github.com/mbutterick/fontkit/blob/master/src/opentype/GSUBProcessor.js
|#
(define-subclass OTProcessor (GSUBProcessor)
(define/override (applyLookup lookupType table)
#;(report lookupType 'GSUBProcessor:applyLookup)
(case lookupType
[(1) ;; Single Substitution
#;(report 'single-substitution)
(define index (send this coverageIndex (· table coverage)))
(cond
[(= index -1) #f]
[else (define glyph (· this glyphIterator cur))
(send glyph id
(case (· table version)
[(1) (bitwise-and (+ (· glyph id) (· table deltaGlyphID)) #xffff)]
[(2) (send (· table substitute) get index)]))
#t])]
[(2) ;; Multiple Substitution
#;(report 'multiple-substitution)
(define index (send this coverageIndex (· table coverage)))
(cond
[(= index -1) #f]
[else (define sequence (send (· table sequences) get index))
(send (· this glyphIterator cur) id (list-ref sequence 0))
(set-field! ligatureComponent (· this glyphIterator cur) 0)
(define features (· this glyphIterator cur features))
(define curGlyph (· this glyphIterator cur))
(define replacement (for/list ([(gid i) (in-indexed (cdr sequence))])
(define glyph (+GlyphInfo (· this font) gid #f features))
(set-field! shaperInfo glyph (· curGlyph shaperInfo))
(set-field! isLigated glyph (· curGlyph isLigated))
(set-field! ligatureComponent glyph (add1 i))
(set-field! substituted glyph #t)
glyph))
(set-field! glyphs this (let-values ([(head tail) (split-at (· this glyphs) (add1 (· this glyphIterator index)))])
(append head replacement tail)))
#t])]
[(3) ;; Alternate substitution
#;(report 'alternate-substitution)
(define index (send this coverageIndex (· table coverage)))
(cond
[(= index -1) #f]
[else (define USER_INDEX 0)
(send (· this glyphIterator cur) id (list-ref (send (· table alternateSet) get index) USER_INDEX))
#t])]
[(4) ;; Ligature substitution
#;(report '---------------------------)
#;(report 'ligature-substitution)
#;(report* lookupType (· table coverage glyphs))
(define index (send this coverageIndex (· table coverage)))
#;(report index 'forker)
(cond
[(= index -1) #f]
[(for*/or ([ligature (in-list (send (· table ligatureSets) get index))]
[matched (in-value (send this sequenceMatchIndices 1 (· ligature components)))]
#:when matched)
(define curGlyph (· this glyphIterator cur))
;; Concatenate all of the characters the new ligature will represent
(define characters
(append (· curGlyph codePoints)
(append* (for/list ([index (in-list matched)])
index
(get-field codePoints (list-ref (· this glyphs) index))))))
characters
;; Create the replacement ligature glyph
(define ligatureGlyph (+GlyphInfo (· this font) (· ligature glyph) characters (· curGlyph features)))
(· ligatureGlyph id)
(set-field! shaperInfo ligatureGlyph (· curGlyph shaperInfo))
(set-field! isLigated ligatureGlyph #t)
(set-field! substituted ligatureGlyph #t)
(define isMarkLigature
(and (· curGlyph isMark)
(for/and ([match-idx (in-list matched)])
(· (list-ref (· this glyphs) match-idx) isMark))))
(set-field! ligatureID ligatureGlyph (cond
[isMarkLigature #f]
[else (define id (· this ligatureID))
(increment-field! ligatureID this)
id]))
(define lastLigID (· curGlyph ligatureID))
(define lastNumComps (length (· curGlyph codePoints)))
(define curComps lastNumComps)
(define idx (add1 (· this glyphIterator index)))
;; Set ligatureID and ligatureComponent on glyphs that were skipped in the matched sequence.
;; This allows GPOS to attach marks to the correct ligature components.
(for ([matchIndex (in-list matched)])
;; Don't assign new ligature components for mark ligatures (see above)
(cond
[isMarkLigature (set! idx matchIndex)]
[else (while (< idx matchIndex)
(define ligatureComponent (+ curComps (- lastNumComps) (min (or (get-field ligatureComponent (list-ref (· this glyphs) idx)) 1) lastNumComps)))
(set-field! ligatureID (list-ref (· this glyphs) idx) (· ligatureGlyph ligatureID))
(set-field! ligatureComponent (list-ref (· this glyphs) idx) ligatureComponent)
(increment! idx))])
(define lastLigID (· (list-ref (· this glyphs) idx) ligatureID))
(define lastNumComps (length (· (list-ref (· this glyphs) idx) codePoints)))
(increment! curComps lastNumComps)
(increment! idx)) ;; skip base glyph
;; Adjust ligature components for any marks following
(when (and lastLigID (not isMarkLigature))
(for ([i (in-range idx (length (· this glyphs)))]
#:when (= (· (list-ref (· this glyphs) idx) ligatureID) lastLigID))
(define ligatureComponent (+ curComps (- lastNumComps) (min (or (get-field ligatureComponent (list-ref (· this glyphs) i)) 1) lastNumComps)))
(set-field! ligatureComponent (list-ref (· this glyphs) i) ligatureComponent)))
;; Delete the matched glyphs, and replace the current glyph with the ligature glyph
#;(report (for/list ([g (· this glyphs)]) (· g id)) 'step-a)
#;(report matched)
#;(report (· this glyphIterator index))
(set-field! glyphs this (for*/list ([(glyph idx) (in-indexed (· this glyphs))]
[midx (in-list matched)]
#:unless (= idx midx))
(if (= idx (· this glyphIterator index))
ligatureGlyph
glyph)))
(set-field! glyphs (· this glyphIterator) (· this glyphs)) ; update glyph iterator to keep it in sync <sigh>
#;(report (for/list ([g (· this glyphs)]) (· g id)) 'step-c)
#;(report (· this glyphIterator index))
#t)]
[else #f])]
[(5) ;; Contextual Substitution
(send this applyContext table)]
[(6) ;; Chaining Contextual Substitution
(send this applyChainingContext table)]
[(7) ;; Extension Substitution
(send this applyLookup (· table lookupType) (· table extension))]
[else (error 'unimplemented-gsub-lookup)])))

@ -0,0 +1,60 @@
#lang fontkit/racket
(require xenomorph)
(provide (all-defined-out))
#|
approximates
https://github.com/mbutterick/fontkit/blob/master/src/tables/head.js
|#
(define-subclass Struct (Rhead))
(define head (make-object Rhead
(dictify
'version int32be ;; 0x00010000 (version 1.0)
'revision int32be ;; set by font manufacturer
'checkSumAdjustment uint32be
'magicNumber uint32be ;; set to 0x5F0F3CF5
'flags uint16be
'unitsPerEm uint16be ;; range from 64 to 16384
'created (+Array int32be 2)
'modified (+Array int32be 2)
'xMin int16be ;; for all glyph bounding boxes
'yMin int16be ;; for all glyph bounding boxes
'xMax int16be ;; for all glyph bounding boxes
'yMax int16be ;; for all glyph bounding boxes
'macStyle (+Bitfield uint16be '(bold italic underline outline shadow condensed extended))
'lowestRecPPEM uint16be ;; smallest readable size in pixels
'fontDirectionHint int16be
'indexToLocFormat int16be ;; 0 for short offsets 1 for long
'glyphDataFormat int16be ;; 0 for current format
)))
(test-module
(require racket/serialize)
(define ip (open-input-file charter-italic-path))
(define dir (deserialize (read (open-input-file charter-italic-directory-path))))
(define offset (· dir tables head offset))
(define length (· dir tables head length))
(check-equal? offset 236)
(check-equal? length 54)
(define table-bytes #"\0\1\0\0\0\2\0\0.\252t<_\17<\365\0\t\3\350\0\0\0\0\316\3\301\261\0\0\0\0\316\3\304\364\377\36\377\24\4\226\3\324\0\2\0\t\0\2\0\0\0\0")
(set-port-position! ip 0)
(check-equal? (peek-bytes length offset ip) table-bytes)
(define table-data (send head decode table-bytes))
(check-equal? (· table-data unitsPerEm) 1000)
(check-equal? (· table-data yMin) -236)
(check-equal? (· table-data yMax) 980)
(check-equal? (· table-data xMax) 1174)
(check-equal? (· table-data xMin) -226)
(check-equal? (· table-data macStyle) (make-hasheq '((shadow . #f)
(extended . #f)
(condensed . #f)
(underline . #f)
(outline . #f)
(bold . #f)
(italic . #t))))
(check-equal? (· table-data magicNumber) #x5F0F3CF5)
(check-equal? (· table-data indexToLocFormat) 0) ; used in loca table
(check-equal? (encode head table-data #f) table-bytes))

@ -0,0 +1,27 @@
#lang racket/base
(require (for-syntax racket/base) racket/runtime-path br/define)
(provide (all-defined-out))
(define index? (λ (x) (and (number? x) (integer? x) (not (negative? x)))))
(define-runtime-path charter-path "../ptest/assets/charter.ttf")
(define-runtime-path charter-italic-path "../ptest/assets/charter-italic.ttf")
(define-runtime-path fira-path "../ptest/assets/fira.ttf")
(define-runtime-path fira-otf-path "../ptest/assets/fira.otf")
(define-runtime-path charter-directory-path "charter-directory.rktd")
(define-runtime-path charter-italic-directory-path "charter-italic-directory.rktd")
(define-macro (test-module . EXPRS)
#`(module+ test
(require #,(datum->syntax caller-stx 'rackunit) #,(datum->syntax caller-stx 'racket/serialize))
. EXPRS))
(define (is-mark? codepoint)
;; mark classes = Mn Me Mc
(regexp-match #px"\\p{Mn}|\\p{Me}|\\p{Mc}" (string (integer->char codepoint))))
(module+ test
(require rackunit)
(check-true (and (is-mark? #x300) #t))
(check-false (and (is-mark? #x2ee) #t)))

@ -0,0 +1,39 @@
#lang fontkit/racket
(require xenomorph)
(provide (all-defined-out))
(define-subclass Struct (Rhhea))
(define hhea (make-object Rhhea
(dictify
'version int32be
'ascent int16be ;; Distance from baseline of highest ascender
'descent int16be ;; Distance from baseline of lowest descender
'lineGap int16be ;; Typographic line gap
'advanceWidthMax uint16be ;; Maximum advance width value in 'hmtx' table
'minLeftSideBearing int16be ;; Maximum advance width value in 'hmtx' table
'minRightSideBearing int16be ;; Minimum right sidebearing value
'xMaxExtent int16be
'caretSlopeRise int16be ;; Used to calculate the slope of the cursor (rise/run); 1 for vertical
'caretSlopeRun int16be ;; 0 for vertical
'caretOffset int16be ;; Set to 0 for non-slanted fonts
'reserved (+Array int16be 4)
'metricDataFormat int16be ;; 0 for current format
'numberOfMetrics uint16be ;; Number of advance widths in 'hmtx' table
)))
(test-module
(define ip (open-input-file charter-path))
(define dir (deserialize (read (open-input-file charter-directory-path))))
(define offset (· dir tables hhea offset))
(define length (· dir tables hhea length))
(check-equal? offset 292)
(check-equal? length 36)
(define table-bytes #"\0\1\0\0\3\324\377\22\0\0\4\311\377_\377`\4\251\0\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\345")
(set-port-position! ip 0)
(check-equal? (peek-bytes length offset ip) table-bytes)
(define table-data (decode hhea table-bytes))
(check-equal? (· table-data ascent) 980)
(check-equal? (· table-data descent) -238)
(check-equal? (· table-data numberOfMetrics) 229))

@ -0,0 +1,44 @@
#lang fontkit/racket
(require xenomorph)
(provide (all-defined-out))
#|
approximates
https://github.com/mbutterick/fontkit/blob/master/src/tables/hmtx.js
|#
(define-subclass Struct (Rhmtx))
(define HmtxEntry (+Struct
(dictify
'advance uint16be
'bearing int16be)))
(define hmtx (+Rhmtx
(dictify
'metrics (+LazyArray HmtxEntry (λ (this-array) (· this-array parent hhea numberOfMetrics)))
'bearings (+LazyArray int16be (λ (this-array) (- (· this-array parent maxp numGlyphs)
(· this-array parent hhea numberOfMetrics)))))))
(test-module
;; same as hmtx but doesn't require resolution of function to get length
(define hmtx-test (+Rhmtx
(dictify
'metrics (+LazyArray HmtxEntry (λ (t) 229))
'bearing (+LazyArray int16be (λ (t) 0)))))
(define ip (open-input-file charter-path))
(define dir (deserialize (read (open-input-file charter-directory-path))))
(define hmtx-offset (· dir tables hmtx offset))
(define hmtx-length (· dir tables hmtx length))
(check-equal? hmtx-offset 456)
(check-equal? hmtx-length 916)
(define hmtx-bytes (peek-bytes hmtx-length hmtx-offset ip))
(define hmtx-data (decode hmtx-test hmtx-bytes))
(check-equal? (send hmtx-test size) (* 229 (send HmtxEntry size)))
(define H-gid 41) (define OE-gid 142)
(check-equal? (dump (send (· hmtx-data metrics) get H-gid)) '#hasheq((advance . 738) (bearing . 33)))
(check-equal? (dump (send (· hmtx-data metrics) get OE-gid)) '#hasheq((advance . 993) (bearing . 43)))
)

@ -0,0 +1,129 @@
#lang fontkit/racket
(require (prefix-in Script- "script.rkt") "glyph.rkt" "glyphrun.rkt" "glyph-position.rkt" "ot-layout-engine.rkt")
(provide (all-defined-out))
#|
approximates
https://github.com/mbutterick/fontkit/blob/master/src/layout/LayoutEngine.js
|#
(define-subclass object% (LayoutEngine font)
(field [unicodeLayoutEngine #f]
[kernProcessor #f]
[engine
;; Choose an advanced layout engine.
;; We try the AAT morx table first since more
;; scripts are currently supported because
;; the shaping logic is built into the font.
(cond
[(· this font has-morx-table?) (error 'morx-layout-unimplemented)]
[(or (· this font has-gsub-table?) (· this font has-gpos-table?))
#;(report/file 'starting-layout-engine)
(+OTLayoutEngine (· this font))]
[else #f])])
(as-methods
layout
substitute
position
hideDefaultIgnorables
isDefaultIgnorable))
(define/contract (layout this str-or-glyphs [features #f]
;; Attempt to detect the script if not provided.
[script (if (string? str-or-glyphs)
(Script-forString str-or-glyphs)
(Script-forCodePoints (append-map (λ (g) (· g codePoints)) str-or-glyphs)))]
[language #f])
(((or/c string? (listof Glyph?))) ((option/c list?) (option/c symbol?) (option/c symbol?)) . ->*m . GlyphRun?)
(define glyphs
;; Map string to glyphs if needed
(if (string? str-or-glyphs)
(send (· this font) glyphsForString str-or-glyphs)
str-or-glyphs))
#;(report*/file 'starting-layout-in-layout-engine glyphs)
(cond
[(empty? glyphs) (+GlyphRun glyphs empty)] ; Return early if there are no glyphs
[else
;; Setup the advanced layout engine
(when (and (· this engine) #;(·? engine setup))
(send (· this engine) setup glyphs features script language))
;; Substitute and position the glyphs
(set! glyphs (send this substitute glyphs features script language))
#;(report*/file 'end-sub glyphs)
#;(error 'stop)
#;(report/file 'ready-position)
#;(report (for/list ((g (in-list glyphs))) (· g id)) 'shecky)
(define positions (send this position glyphs features script language))
#;(report (for/list ((p (in-list positions))) (list (· p xAdvance) (· p xOffset))))
#;(report/file 'fired-position)
;; Let the layout engine clean up any state it might have
(when (and (· this engine) #;(·? this engine cleanup))
(· this engine cleanup))
(+GlyphRun glyphs positions)]))
(define (substitute this glyphs features script language)
#;((is-a?/c GlyphRun) . ->m . void?)
;; Call the advanced layout engine to make substitutions
(when (and (· this engine) #;(· this engine substitute))
(set! glyphs (send (· this engine) substitute glyphs features script language)))
#;(report/file glyphs)
glyphs)
(define/contract (position this glyphs features script language)
((listof Glyph?) (option/c list?) (option/c symbol?) (option/c symbol?) . ->m . (listof GlyphPosition?))
(define positions (for/list ([glyph (in-list glyphs)])
(make-object GlyphPosition (· glyph advanceWidth))))
;; Call the advanced layout engine. Returns the features applied.
(define positioned
(and (· this engine) #;(· this engine position)
(send (· this engine) position glyphs positions features script language)))
;; if there is no GPOS table, use unicode properties to position marks.
;; todo: unicode layout
#;(unless positioned
(unless (· this unicodeLayoutEngine)
(set! unicodeLayoutEngine (+UnicodeLayoutEngine (· this font))))
(send unicodeLayoutEngine positionGlyphs glyphs positions))
;; if kerning is not supported by GPOS, do kerning with the TrueType/AAT kern table
;; todo: old style kern table
#;(when (and (or (not positioned) (not (· positioned kern))) (· this font kern))
(unless kernProcessor
(set! kernProcessor (+KernProcessor (· this font))))
(send kernProcessor process glyphs positions))
positions
)
(define/contract (hideDefaultIgnorables this glyphRun)
((is-a?/c GlyphRun) . ->m . void?)
(define space (send (· this font) glyphForCodePoint #x20))
(define-values (new-glyphs new-positions)
(for/lists (ngs nps)
([glyph (in-list (· glyphRun glyphs))]
[pos (in-list (· glyphRun positions))])
(cond
[(send this isDefaultIgnorable (car (· glyph codePoints)))
(define new-pos pos)
(set-field! xAdvance new-pos 0)
(set-field! yAdvance new-pos 0)
(values space new-pos)]
[else (values glyph pos)])))
(set-field! glyphs glyphRun new-glyphs)
(set-field! positions glyphRun new-positions))
(define/contract (isDefaultIgnorable this codepoint)
(index? . ->m . boolean?)
#f ; todo: everything
)

@ -0,0 +1,62 @@
#lang fontkit/racket
(require xenomorph)
(provide (all-defined-out))
(define 16bit-style 0)
(define 32bit-style 1)
(define max-32-bit-value #xffff)
#|
approximates
https://github.com/mbutterick/fontkit/blob/master/src/tables/loca.js
|#
(define-subclass VersionedStruct (Rloca)
(define/augride (post-decode res stream ctx)
;; in `xenomorph` `process` method, `res` is aliased as `this`
;;
(when (= 16bit-style (· res version))
;; in a 16bits-style loca table, actual 32bit offset values are divided by 2 (to fit into 16 bits)
;; so we re-inflate them.
(dict-update! res 'offsets (λ (offsets) (map (curry * 2) offsets))))
res)
(define/augride (pre-encode this-val stream)
;; this = val to be encoded
(loca-pre-encode this-val stream)
this-val))
;; make "static method"
(define (loca-pre-encode this . args)
;; this = val to be encoded
(unless (dict-has-key? this 'version)
(dict-set! this 'version (if (> (last (· this offsets)) max-32-bit-value)
32bit-style
16bit-style))
(when (= 16bit-style (· this version))
(dict-update! this 'offsets (λ (offsets) (map (curryr / 2) offsets))))))
(define loca (+Rloca
(λ (o) (· o head indexToLocFormat))
(dictify
0 (dictify 'offsets (+Array uint16be))
1 (dictify 'offsets (+Array uint32be)))))
(test-module
(define ip (open-input-file charter-path))
(define dir (deserialize (read (open-input-file charter-directory-path))))
(define offset (· dir tables loca offset))
(define len (· dir tables loca length))
(check-equal? offset 38692)
(check-equal? len 460)
(define ds (peek-bytes len offset ip))
(check-equal?
(send loca encode #f (mhash 'version 0 'offsets '(0 76 156))) #"\0\0\0L\0\234")
(check-equal?
(send loca encode #f '#hash((version . 1) (offsets . (0 76 156)))) #"\0\0\0\0\0\0\0L\0\0\0\234")
(send loca force-version! 0)
(define table-data (send loca decode ds))
(check-equal? (length (· table-data offsets)) 230)
(check-equal? (· table-data offsets) '(0 0 0 136 296 500 864 1168 1548 1628 1716 1804 1944 2048 2128 2176 2256 2312 2500 2596 2788 3052 3168 3396 3624 3732 4056 4268 4424 4564 4640 4728 4804 5012 5384 5532 5808 6012 6212 6456 6672 6916 7204 7336 7496 7740 7892 8180 8432 8648 8892 9160 9496 9764 9936 10160 10312 10536 10780 10992 11148 11216 11272 11340 11404 11444 11524 11820 12044 12216 12488 12728 12932 13324 13584 13748 13924 14128 14232 14592 14852 15044 15336 15588 15776 16020 16164 16368 16520 16744 16984 17164 17320 17532 17576 17788 17896 18036 18284 18552 18616 18988 19228 19512 19712 19796 19976 20096 20160 20224 20536 20836 20876 21000 21200 21268 21368 21452 21532 21720 21908 22036 22244 22664 22872 22932 22992 23088 23220 23268 23372 23440 23600 23752 23868 23988 24084 24184 24224 24548 24788 25012 25292 25716 25884 26292 26396 26540 26796 27172 27488 27512 27536 27560 27584 27912 27936 27960 27984 28008 28032 28056 28080 28104 28128 28152 28176 28200 28224 28248 28272 28296 28320 28344 28368 28392 28416 28440 28464 28488 28512 28536 28560 28968 28992 29016 29040 29064 29088 29112 29136 29160 29184 29208 29232 29256 29280 29304 29328 29352 29376 29400 29424 29448 29472 29496 29520 29824 30164 30220 30652 30700 30956 31224 31248 31332 31488 31636 31916 32104 32176 32484 32744 32832 32956 33248 33664 33884 34048 34072))
)

@ -0,0 +1,7 @@
#lang fontkit/racket
(r+p "font.rkt"
"glyph-position.rkt"
"subset.rkt"
"bbox.rkt"
"tables.rkt")

@ -0,0 +1,38 @@
#lang fontkit/racket
(require xenomorph)
(provide (all-defined-out))
(define-subclass Struct (Rmaxp))
(define maxp (make-object Rmaxp
(dictify 'version int32be
'numGlyphs uint16be ;; The number of glyphs in the font
'maxPoints uint16be ;; Maximum points in a non-composite glyph
'maxContours uint16be ;; Maximum contours in a non-composite glyph
'maxComponentPoints uint16be ;; Maximum points in a composite glyph
'maxComponentContours uint16be ;; Maximum contours in a composite glyph
'maxZones uint16be ;; 1 if instructions do not use the twilight zone, 2 otherwise
'maxTwilightPoints uint16be ;; Maximum points used in Z0
'maxStorage uint16be ;; Number of Storage Area locations
'maxFunctionDefs uint16be ;; Number of FDEFs
'maxInstructionDefs uint16be ;; Number of IDEFs
'maxStackElements uint16be ;; Maximum stack depth
'maxSizeOfInstructions uint16be ;; Maximum byte count for glyph instructions
'maxComponentElements uint16be ;; Maximum number of components referenced at “top level” for any composite glyph
'maxComponentDepth uint16be ;; Maximum levels of recursion; 1 for simple components
)))
(test-module
(define ip (open-input-file charter-path))
(define dir (deserialize (read (open-input-file charter-directory-path))))
(define maxp-offset (· dir tables maxp offset))
(define maxp-length (· dir tables maxp length))
(check-equal? maxp-offset 328)
(check-equal? maxp-length 32)
(define maxp-bytes #"\0\1\0\0\0\345\0f\0\a\0O\0\4\0\1\0\0\0\0\0\n\0\0\2\0\1s\0\2\0\1")
(set-port-position! ip 0)
(check-equal? (peek-bytes maxp-length maxp-offset ip) maxp-bytes)
(define maxp-data (send maxp decode maxp-bytes))
(check-equal? (· maxp-data numGlyphs) 229)
(check-equal? (· maxp-data version) 65536))

@ -0,0 +1,194 @@
#lang fontkit/racket
(require xenomorph)
(provide (all-defined-out))
#|
approximates
https://github.com/mbutterick/fontkit/blob/master/src/tables/opentype.js
|#
;;########################
;; Scripts and Languages #
;;########################
(define-subclass Array (FeatIdxArray))
(define LangSysTable (+Struct
(dictify 'reserved uint16be
'reqFeatureIndex uint16be
'featureCount uint16be
'featureIndexes (+FeatIdxArray uint16be 'featureCount))))
(define-subclass Pointer (LSR-Pointer))
(define LangSysRecord (+Struct
(dictify 'tag (+Symbol 4)
'langSys (+LSR-Pointer uint16be LangSysTable (mhash 'type 'parent)))))
(define-subclass Pointer (DLS-Pointer))
(define-subclass Array (DLS-Array))
(define Script (+Struct
(dictify 'defaultLangSys (+DLS-Pointer uint16be LangSysTable)
'count uint16be
'langSysRecords (+DLS-Array LangSysRecord 'count))))
(define-subclass Struct (ScriptRecord-Struct))
(define-subclass Pointer (ScriptRecord-Pointer))
(define ScriptRecord (+ScriptRecord-Struct
(dictify 'tag (+Symbol 4)
'script (+ScriptRecord-Pointer uint16be Script (mhash 'type 'parent)))))
(define ScriptList (+Array ScriptRecord uint16be))
;;#######################
;; Features and Lookups #
;;#######################
(define Feature (+Struct (dictify
'featureParams uint16be
'lookupCount uint16be
'lookupListIndexes (+Array uint16be 'lookupCount))))
(define-subclass Struct (FeatureRec))
(define-subclass Pointer (FeatureRec-Pointer))
(define FeatureRecord (+FeatureRec (dictify
'tag (+Symbol 4)
'feature (+FeatureRec-Pointer uint16be Feature (mhash 'type 'parent)))))
(define FeatureList (+Array FeatureRecord uint16be))
(define LookupFlags (+Bitfield uint16be '(rightToLeft ignoreBaseGlyphs ignoreLigatures ignoreMarks useMarkFilteringSet #f markAttachmentType)))
(define (LookupList SubTable)
(define Lookup (+Struct
(dictify
'lookupType uint16be
'flags LookupFlags
'subTableCount uint16be
'subTables (+Array (+Pointer uint16be SubTable) 'subTableCount)
'markFilteringSet uint16be)))
(+LazyArray (+Pointer uint16be Lookup) uint16be))
;;#################
;; Coverage Table #
;;#################
(define RangeRecord
(+Struct
(dictify
'start uint16be
'end uint16be
'startCoverageIndex uint16be)))
(define Coverage
(+VersionedStruct uint16be
(dictify
1 (dictify
'glyphCount uint16be
'glyphs (+Array uint16be 'glyphCount))
2 (dictify
'rangeCount uint16be
'rangeRecords (+Array RangeRecord 'rangeCount)))))
;;#########################
;; Class Definition Table #
;;#########################
(define ClassRangeRecord (+Struct
(dictify
'start uint16be
'end uint16be
'class uint16be)))
(define ClassDef (+VersionedStruct uint16be
(dictify
1 ;; Class array
(dictify
'startGlyph uint16be
'glyphCount uint16be
'classValueArray (+Array uint16be 'glyphCount))
2 ;; Class ranges
(dictify
'classRangeCount uint16be
'classRangeRecord (+Array ClassRangeRecord 'classRangeCount)))))
;;###############
;; Device Table #
;;###############
(define Device (+Struct
(dictify
'startSize uint16be
'endSize uint16be
'deltaFormat uint16be)))
;;#############################################
;; Contextual Substitution/Positioning Tables #
;;#############################################
(define LookupRecord (+Struct
(dictify
'sequenceIndex uint16be
'lookupListIndex uint16be)))
(define Context
(+VersionedStruct
uint16be
(dictify
;; Simple context
1 (dictify
'coverage (+Pointer uint16be Coverage)
'ruleSetCount uint16be
'ruleSets (+Array (+Pointer uint16be 'RuleSet) 'ruleSetCount))
;; Class-based context
2 (dictify
'coverage (+Pointer uint16be Coverage)
'classDef (+Pointer uint16be 'ClassDef)
'classSetCnt uint16be
'classSet (+Array (+Pointer uint16be 'ClassSet) 'classSetCnt))
3 (dictify
'glyphCount uint16be
'lookupCount uint16be
'coverages (+Array (+Pointer uint16be Coverage) 'glyphCount)
'lookupRecords (+Array LookupRecord 'lookupCount)))))
;;######################################################
;; Chaining Contextual Substitution/Positioning Tables #
;;######################################################
(define ChainingContext
(+VersionedStruct
uint16be
(dictify
;; Simple context glyph substitution
1 (dictify
'coverage (+Pointer uint16be Coverage)
'chainCount uint16be
'chainRuleSets (+Array (+Pointer uint16be 'ChainRuleSet) 'chainCount))
;; Class-based chaining context
2 (dictify
'coverage (+Pointer uint16be Coverage)
'backtrackClassDef (+Pointer uint16be 'ClassDef)
'inputClassDef (+Pointer uint16be 'ClassDef)
'lookaheadClassDef (+Pointer uint16be 'ClassDef)
'chainCount uint16be
'chainClassSet (+Array (+Pointer uint16be 'ChainRuleSet) 'chainCount))
;; Coverage-based chaining context
3 (dictify
'backtrackGlyphCount uint16be
'backtrackCoverage (+Array (+Pointer uint16be Coverage) 'backtrackGlyphCount)
'inputGlyphCount uint16be
'inputCoverage (+Array (+Pointer uint16be Coverage) 'inputGlyphCount)
'lookaheadGlyphCount uint16be
'lookaheadCoverage (+Array (+Pointer uint16be Coverage) 'lookaheadGlyphCount)
'lookupCount uint16be
'lookupRecords (+Array LookupRecord 'lookupCount)))))

@ -0,0 +1,84 @@
#lang fontkit/racket
(require "gsub-processor.rkt" "gpos-processor.rkt" "glyphinfo.rkt" (prefix-in Shapers- "shapers.rkt") "shaping-plan.rkt")
(provide (all-defined-out))
#|
https://github.com/mbutterick/fontkit/blob/master/src/opentype/OTLayoutEngine.js
|#
(define-subclass object% (OTLayoutEngine font)
(field [glyphInfos #f]
[shaper #f]
[plan #f]
[GSUBProcessor #f]
[GPOSProcessor #f])
#;(report/file 'starting-ot-layout-engine)
(when (· font has-gsub-table?)
(set-field! GSUBProcessor this (+GSUBProcessor font (or (· font GSUB) (error 'no-gsub-table)))))
(when (· font has-gpos-table?)
(set-field! GPOSProcessor this (+GPOSProcessor font (or (· font GPOS) (error 'no-gpos-table)))))
(define/public (setup glyphs features script language)
;; Map glyphs to GlyphInfo objects so data can be passed between
;; GSUB and GPOS without mutating the real (shared) Glyph objects.
(set! glyphInfos (map (λ (glyph) (+GlyphInfo (· this font) (· glyph id) (· glyph codePoints))) glyphs))
;; Choose a shaper based on the script, and setup a shaping plan.
;; This determines which features to apply to which glyphs.
(set! shaper (Shapers-choose script))
(set! plan (+ShapingPlan (· this font) script language))
#;(report/file shaper)
(send (make-object shaper) plan (· this plan) (· this glyphInfos) features))
(define/public (substitute glyphs . _)
(cond
[(· this GSUBProcessor)
#;(report/file (· this glyphInfos))
(define new-glyphinfos
(send (· this plan) process (· this GSUBProcessor) (· this glyphInfos)))
(set! glyphInfos new-glyphinfos) ; update OTLayoutEngine state for positioning pass
#;(report/file new-glyphinfos)
;; Map glyph infos back to normal Glyph objects
#;(report/file (for/list ([glyphInfo (in-list new-glyphinfos)])
(send (· this font) getGlyph (· glyphInfo id) (· glyphInfo codePoints))))
(for/list ([glyphInfo (in-list new-glyphinfos)])
(send (· this font) getGlyph (· glyphInfo id) (· glyphInfo codePoints)))]
[else glyphs]))
(define/public (position glyphs positions . _)
#;(report*/file glyphs positions shaper)
(define static-shaper (make-object shaper))
(when (eq? (· static-shaper zeroMarkWidths) 'BEFORE_GPOS)
(zeroMarkAdvances positions))
(when GPOSProcessor
#;(report/file GPOSProcessor)
(send (· this plan) process GPOSProcessor glyphInfos positions))
(when (eq? (· static-shaper zeroMarkWidths) 'AFTER_GPOS)
(zeroMarkAdvances positions))
;; Reverse the glyphs and positions if the script is right-to-left
(when (eq? (· this plan direction) 'rtl)
(set! glyphs (reverse glyphs))
(set! positions (reverse positions)))
#;(report/file (and GPOSProcessor (· GPOSProcessor features)))
(and GPOSProcessor (· GPOSProcessor features)))
(define/public (zeroMarkAdvances positions)
(set! positions
(for/list ([glyphInfo (in-list glyphInfos)]
[position (in-list positions)])
(when (· glyphInfo isMark)
(dict-set*! position
'xAdvance 0
'yAdvance 0))
position)))
)

@ -0,0 +1,250 @@
#lang fontkit/racket
(require (prefix-in Script- "script.rkt") br/cond "glyph-iterator.rkt")
(provide (all-defined-out))
#|
https://github.com/mbutterick/fontkit/blob/master/src/opentype/OTProcessor.js
|#
(define DEFAULT_SCRIPTS '(DFLT dflt latn))
(define-subclass object% (OTProcessor font table)
(field [script #f]
[scriptTag #f]
[language #f]
[languageTag #f]
[features (mhash)]
[lookups (mhash)]
[direction #f]
[glyphIterator #f]) ; appears below
;; initialize to default script + language
(selectScript)
;; current context (set by applyFeatures)
(field [glyphs empty]
[positions empty] ; only used by GPOS
[ligatureID 1])
(define/public (findScript script-or-scripts)
(and (· this table scriptList)
(let ([scripts (if (pair? script-or-scripts) script-or-scripts (list script-or-scripts))])
(for*/first ([entry (in-list (· this table scriptList))]
[s (in-list scripts)]
#:when (eq? (· entry tag) s))
entry))))
(define/public (selectScript [script #f] [language #f])
(let/ec return!
(define changed #f)
(define entry #f)
(when (or (not (· this script)) (not (eq? script (· this scriptTag))))
(set! entry (findScript script))
(when script
(set! entry (findScript script))) ; ? why double dip
(when (not entry)
(set! entry (findScript DEFAULT_SCRIPTS)))
(when (not entry)
(return! (void)))
(set-field! scriptTag this (· entry tag))
(set-field! script this (· entry script))
(set-field! direction this (Script-direction script))
(set-field! language this #f)
(set! changed #t))
(when (and (not language) (not (equal? language (· this languageTag))))
(for/first ([lang (in-list (· this script langSysRecords))]
#:when (equal? (· lang tag) language))
(set-field! language this (· lang langSys))
(set-field! languageTag this (· lang tag))
(set! changed #t)))
(when (not (· this language))
(set-field! language this (· this script defaultLangSys)))
;; Build a feature lookup table
(when changed
(set-field! features this (mhash))
(when (· this language)
(for ([featureIndex (in-list (· this language featureIndexes))])
(define record (list-ref (· this table featureList) featureIndex))
(dict-set! (· this features) (· record tag) (· record feature)))))))
(define/public (lookupsForFeatures [userFeatures empty] [exclude #f])
#;(report*/file 'ot-proc:lookupsForFeatures)
(sort (for*/list ([tag (in-list userFeatures)]
[feature (in-value (dict-ref (· this features) tag #f))]
#:when feature
[lookupIndex (in-list (· feature lookupListIndexes))]
#:unless (and exclude (index-of exclude lookupIndex)))
#;(report*/file tag lookupIndex)
(mhasheq 'feature tag
'index lookupIndex
'lookup (send (· this table lookupList) get lookupIndex)))
< #:key (λ (i) (· i index))))
(define/public (applyFeatures userFeatures glyphs advances)
#;(report/file 'ot-proc:applyFeatures-part1)
(define lookups (send this lookupsForFeatures userFeatures))
#;(report/file 'ot-proc:applyFeatures-part2)
#;(report/file (length glyphs))
(send this applyLookups lookups glyphs advances)
#;(report*/file (length glyphs) (length (· this glyphs)))
(· this glyphs))
(define/public (applyLookups lookups glyphs positions)
(set-field! glyphs this glyphs)
(set-field! positions this positions)
#;(report/file 'ot-proc:applyLookups)
#;(report (for/list ([g (· this glyphs)]) (· g id)) 'glyph-ids)
(set-field! glyphIterator this (+GlyphIterator glyphs))
(for* ([lookup-entry (in-list lookups)])
(define feature (· lookup-entry feature))
(define lookup (· lookup-entry lookup))
#;(report 'resetting-iterator)
(send (· this glyphIterator) reset (· lookup flags))
(while (< (or (· this glyphIterator index) 0) (length (· this glyphs)))
#;(report/file 'start-while++++++++++++++++++)
#;(report (length (· this glyphs)) 'glyphs-length-top)
#;(report (for/list ([g (· this glyphs)]) (· g id)) 'gids-top)
#;(report (· this glyphIterator index) giterator-idx-top)
#;(report* feature (dict-keys (· this glyphIterator cur features)))
#;(report (dict-has-key? (· this glyphIterator cur features) feature))
(cond
[(not (dict-has-key? (· this glyphIterator cur features) feature))
(send (· this glyphIterator) next)]
[else
#;(report 'start-lookup-branch=================)
#;(report* (for/list ([g glyphs]) (· g id)) (for/list ([g (· this glyphs)]) (· g id)) (for/list ([g (· this glyphIterator glyphs)]) (· g id)) (· this glyphIterator index) (· this glyphIterator cur id) (· this glyphIterator peekIndex))
(for/or ([table (in-list (· lookup subTables))])
(send this applyLookup (· lookup lookupType) table))
#;(report 'incrementing-iterator-at-bottom)
(send (· this glyphIterator) next)
#;(report* (· this glyphIterator cur) (· this glyphIterator index))
(· this glyphIterator index)]))))
(abstract applyLookup)
(define/public (applyLookupList lookupRecords)
(report/file 'applyLookupList-not-implemented)
(error))
(define/public (coverageIndex coverage [glyph #f])
(unless glyph (set! glyph (· this glyphIterator cur id)))
(or (case (· coverage version)
[(1) (index-of (· coverage glyphs) glyph)]
[(2) (for/first ([range (in-list (· coverage rangeRecords))]
#:when (<= (· range start) glyph (· range end)))
(+ (· range startCoverageIndex) glyph (- (· range start))))]
[else #f]) -1))
(define/public (match sequenceIndex sequence fn [matched #f])
(define pos (· this glyphIterator index))
(define glyph (send (· this glyphIterator) increment sequenceIndex))
(define idx 0)
#;(report*/file (and (pair? sequence) (list-ref sequence idx)) glyph (and glyph (· glyph id)))
(while (and (< idx (length sequence)) glyph (fn (list-ref sequence idx) (· glyph id)))
#;(report* 'in-match-loop idx (· glyph id))
(when matched
(push-end! matched (· this glyphIterator index)))
(increment! idx)
(set! glyph (· this glyphIterator next)))
(set-field! index (· this glyphIterator) pos)
(cond
[(< idx (length sequence)) #f]
[else (or matched #t)]))
(define/public (sequenceMatchIndices sequenceIndex sequence)
(send this match sequenceIndex sequence (λ (component glyph) (= component glyph)) empty))
(define/public (coverageSequenceMatches sequenceIndex sequence)
#;(report 'in-coverageSequenceMatches)
(send this match sequenceIndex sequence (λ (coverage glyph) (>= (send this coverageIndex coverage glyph) 0))))
(define/public (getClassID glyph classDef)
(or
(case (· classDef version)
[(1) ;; Class array
(define i (- glyph (· classDef startGlyph)))
(and (>= i 0)
(< i (length (· classDef classValueArray)))
(list-ref (· classDef classValueArray) i))]
[(2)
(for/first ([range (in-list (· classDef classRangeRecord))]
#:when (<= (· range start) glyph (· range end)))
(· range class))])
0))
(define/public (applyContext table)
(report/file 'otproc:applyContext)
(case (· table version)
[(1) (define index (send this coverageIndex (· table coverage)))
(cond
[(= index -1) #f]
[else (define set (list-ref (· table ruleSets) index))
(for/first ([rule (in-list set)]
#:when (send this sequenceMatches 1 (· rule input)))
(send this applyLookupList (· rule lookupRecords)))])]
[(2) (cond
[(= (send this coverageIndex (· table coverage)) -1) #f]
[else (define index
(send this getClassID (· this glyphIterator cur id) (· table classDef)))
(cond
[(- index -1) #f]
[else (define set (list-ref (· table classSet) index))
(for/first ([rule (in-list set)]
#:when (send this sequenceMatches 1 (· rule classes) (· table classDef)))
(send this applyLookupList (· rule lookupRecords)))])])]
[(3) (and (send this coverageSequenceMatches 0 (· table coverages))
(send this applyLookupList (· table lookupRecords)))]
[else #f]))
(define/public (applyChainingContext table)
#;(report/file 'otproc:applyChainingContext)
(case (· table version)
[(1)
#;(report 'case-1)
(define index (send this coverageIndex (· table coverage)))
(cond
[(= index -1) #f]
[else (define set (list-ref (· table chainRuleSets) index))
(for/first ([rule (in-list set)]
#:when (and (send this sequenceMatches (- (length (· rule backtrack)) (· rule backtrack)))
(send this sequenceMatches 1 (· rule input))
(send this sequenceMatches (add1 (length (· rule input))) (· rule lookahead))))
(send this applyLookupList (· rule lookupRecords)))])]
[(2)
#;(report 'case-2)
(cond
[(= -1 (send this coverageIndex (· table coverage))) #f]
[else (define index (send this getClassID (· this glyphIterator cur id) (· table inputClassDef)))
(define rules (list-ref (· table chainClassSet) index))
(cond
[(not rules) #f]
[else (for/first ([rule (in-list rules)]
#:when (and (send this classSequenceMatches (- (length (· rule backtrack)) (· rule backtrack) (· table backtrackClassDef)))
(send this classSequenceMatches 1 (· rule input) (· table inputClassDef))
(send this classSequenceMatches (add1 (length (· rule input))) (· rule lookahead) (· table lookaheadClassDef))))
(send this applyLookupList (· rule lookupRecords)))])])]
[(3)
#;(report 'case-3)
(and
(send this coverageSequenceMatches (- (· table backtrackGlyphCount)) (· table backtrackCoverage))
(send this coverageSequenceMatches 0 (· table inputCoverage))
(send this coverageSequenceMatches (· table inputGlyphCount) (· table lookaheadCoverage))
(send this applyLookupList (· table lookupRecords)))]
[else #f]))
)

@ -0,0 +1,48 @@
#lang fontkit/racket
(require xenomorph)
(provide (all-defined-out))
#|
approximates
https://github.com/mbutterick/fontkit/blob/master/src/tables/post.js
|#
(define-subclass VersionedStruct (Rpost))
(define post (make-object Rpost
fixed32be
(dictify
'header (dictify 'italicAngle fixed32be ;; Italic angle in counter-clockwise degrees from the vertical.
'underlinePosition int16be ;; Suggested distance of the top of the underline from the baseline
'underlineThickness int16be ;; Suggested values for the underline thickness
'isFixedPitch uint32be ;; Whether the font is monospaced
'minMemType42 uint32be ;; Minimum memory usage when a TrueType font is downloaded as a Type 42 font
'maxMemType42 uint32be ;; Maximum memory usage when a TrueType font is downloaded as a Type 42 font
'minMemType1 uint32be ;; Minimum memory usage when a TrueType font is downloaded as a Type 1 font
'maxMemType1 uint32be) ;; Maximum memory usage when a TrueType font is downloaded as a Type 1 font
1 null
2 (dictify 'numberOfGlyphs uint16be
'glyphNameIndex (+Array uint16be 'numberOfGlyphs)
'names (+Array (+String uint8))
)
2.5 (dictify 'numberOfGlyphs uint16be
'offsets (+Array uint8))
3 null
4 (dictify 'map (+Array uint32be (λ (t) (· t parent maxp numGlyphs)))))))
(test-module
(define ip (open-input-file charter-path))
(define dir (deserialize (read (open-input-file charter-directory-path))))
(define offset (· dir tables post offset))
(define len (· dir tables post length))
(check-equal? offset 41520)
(check-equal? len 514)
(define ds (open-input-bytes (peek-bytes len offset ip)))
(define version (decode fixed32be ds)) ; version = 2
(send post force-version! version)
(define table-data (decode post ds))
(check-equal? (· table-data underlineThickness) 58)
(check-equal? (· table-data underlinePosition) -178)
(check-equal? (· table-data names) '("periodcentered" "macron")))

@ -0,0 +1,27 @@
#lang fontkit/racket
(require xenomorph)
(provide (all-defined-out))
#|
approximates
https://github.com/mbutterick/fontkit/blob/master/src/tables/prep.js
|#
(define-subclass Struct (Rprep))
(define prep (make-object Rprep
(dictify
'controlValueProgram (+Array uint8))))
(test-module
(define ip (open-input-file charter-path))
(define dir (deserialize (read (open-input-file charter-directory-path))))
(define offset (· dir tables prep offset))
(define len (· dir tables prep length))
(check-equal? offset 4512)
(check-equal? len 78)
(set-port-position! ip 0)
(define table-bytes #"\270\0\0+\0\272\0\1\0\1\0\2+\1\272\0\2\0\1\0\2+\1\277\0\2\0C\0007\0+\0\37\0\23\0\0\0\b+\0\277\0\1\0\200\0i\0R\0;\0#\0\0\0\b+\0\272\0\3\0\5\0\a+\270\0\0 E}i\30D")
(check-equal? table-bytes (peek-bytes len offset ip))
(define ds (open-input-bytes (peek-bytes len offset ip)))
(check-equal? (dict-ref (decode prep ds) 'controlValueProgram) '(184 0 0 43 0 186 0 1 0 1 0 2 43 1 186 0 2 0 1 0 2 43 1 191 0 2 0 67 0 55 0 43 0 31 0 19 0 0 0 8 43 0 191 0 1 0 128 0 105 0 82 0 59 0 35 0 0 0 8 43 0 186 0 3 0 5 0 7 43 184 0 0 32 69 125 105 24 68)))

@ -0,0 +1,34 @@
#lang racket/base
(require (for-syntax racket/base br/syntax))
(provide (for-syntax (all-from-out racket/base br/syntax)))
(provide (all-from-out racket/base) r+p (all-defined-out))
(define-syntax-rule (r+p id ...) (begin (require id ...) (provide (all-from-out id ...))))
(r+p "helper.rkt"
sugar/debug
racket/class
racket/file
racket/match
racket/string
racket/format
racket/contract
racket/dict
racket/list
racket/port
racket/function
br/define
sugar/class
sugar/js
sugar/dict
sugar/stub
sugar/port
sugar/contract
describe)
(module reader syntax/module-reader
#:language 'fontkit/racket
#:read @-read
#:read-syntax @-read-syntax
(require (prefix-in @- scribble/reader)))

@ -0,0 +1,182 @@
#lang fontkit/racket
(provide (all-defined-out))
#|
https://github.com/mbutterick/fontkit/blob/master/src/layout/Script.js
|#
;; This maps the Unicode Script property to an OpenType script tag
;; Data from http://www.microsoft.com/typography/otspec/scripttags.htm
;; and http://www.unicode.org/Public/UNIDATA/PropertyValueAliases.txt.
(define UNICODE_SCRIPTS
(apply mhash
'(Caucasian_Albanian aghb
Arabic arab
Imperial_Aramaic armi
Armenian armn
Avestan avst
Balinese bali
Bamum bamu
Bassa_Vah bass
Batak batk
Bengali '(bng2 beng)
Bopomofo bopo
Brahmi brah
Braille brai
Buginese bugi
Buhid buhd
Chakma cakm
Canadian_Aboriginal cans
Carian cari
Cham cham
Cherokee cher
Coptic copt
Cypriot cprt
Cyrillic cyrl
Devanagari '(dev2 deva)
Deseret dsrt
Duployan dupl
Egyptian_Hieroglyphs egyp
Elbasan elba
Ethiopic ethi
Georgian geor
Glagolitic glag
Gothic goth
Grantha gran
Greek grek
Gujarati '(gjr2 gujr)
Gurmukhi '(gur2 guru)
Hangul hang
Han hani
Hanunoo hano
Hebrew hebr
Hiragana hira
Pahawh_Hmong hmng
Katakana_Or_Hiragana hrkt
Old_Italic ital
Javanese java
Kayah_Li kali
Katakana kana
Kharoshthi khar
Khmer khmr
Khojki khoj
Kannada '(knd2 knda)
Kaithi kthi
Tai_Tham lana
Lao lao
Latin latn
Lepcha lepc
Limbu limb
Linear_A lina
Linear_B linb
Lisu lisu
Lycian lyci
Lydian lydi
Mahajani mahj
Mandaic mand
Manichaean mani
Mende_Kikakui mend
Meroitic_Cursive merc
Meroitic_Hieroglyphs mero
Malayalam '(mlm2 mlym)
Modi modi
Mongolian mong
Mro mroo
Meetei_Mayek mtei
Myanmar '(mym2 mymr)
Old_North_Arabian narb
Nabataean nbat
Nko nko
Ogham ogam
Ol_Chiki olck
Old_Turkic orkh
Oriya orya
Osmanya osma
Palmyrene palm
Pau_Cin_Hau pauc
Old_Permic perm
Phags_Pa phag
Inscriptional_Pahlavi phli
Psalter_Pahlavi phlp
Phoenician phnx
Miao plrd
Inscriptional_Parthian prti
Rejang rjng
Runic runr
Samaritan samr
Old_South_Arabian sarb
Saurashtra saur
Shavian shaw
Sharada shrd
Siddham sidd
Khudawadi sind
Sinhala sinh
Sora_Sompeng sora
Sundanese sund
Syloti_Nagri sylo
Syriac syrc
Tagbanwa tagb
Takri takr
Tai_Le tale
New_Tai_Lue talu
Tamil taml
Tai_Viet tavt
Telugu '(tel2 telu)
Tifinagh tfng
Tagalog tglg
Thaana thaa
Thai thai
Tibetan tibt
Tirhuta tirh
Ugaritic ugar
Vai vai
Warang_Citi wara
Old_Persian xpeo
Cuneiform xsux
Yi yi
Inherited zinh
Common zyyy
Unknown zzzz)))
(define/contract (fromUnicode script)
((option/c symbol?) . -> . symbol?)
(hash-ref UNICODE_SCRIPTS script #f))
(define-stub-stop forString)
(define-stub-stop forCodePoints)
(define RTL '( arab ;; Arabic
hebr ;; Hebrew
syrc ;; Syriac
thaa ;; Thaana
cprt ;; Cypriot Syllabary
khar ;; Kharosthi
phnx ;; Phoenician
|nko | ;; N'Ko
lydi ;; Lydian
avst ;; Avestan
armi ;; Imperial Aramaic
phli ;; Inscriptional Pahlavi
prti ;; Inscriptional Parthian
sarb ;; Old South Arabian
orkh ;; Old Turkic, Orkhon Runic
samr ;; Samaritan
mand ;; Mandaic, Mandaean
merc ;; Meroitic Cursive
mero ;; Meroitic Hieroglyphs
;; Unicode 7.0 (not listed on http://www.microsoft.com/typography/otspec/scripttags.htm)
mani ;; Manichaean
mend ;; Mende Kikakui
nbat ;; Nabataean
narb ;; Old North Arabian
palm ;; Palmyrene
phlp ;; Psalter Pahlavi
))
(define/contract (direction script)
((option/c symbol?) . -> . (or/c 'rtl 'ltr))
(if (memq script RTL) 'rtl 'ltr))

@ -0,0 +1,73 @@
#lang fontkit/racket
(require "default-shaper.rkt")
(provide (all-defined-out))
#|
approximates
https://github.com/mbutterick/fontkit/blob/master/src/opentype/shapers/index.js
|#
;; todo: alternative shapers
(define SHAPERS
(hasheq
; 'arab ArabicShaper ;; Arabic
; 'mong ArabicShaper ;; Mongolian
; 'syrc ArabicShaper ;; Syriac
; '|nko | ArabicShaper ;; N'Ko
; 'phag ArabicShaper ;; Phags Pa
; 'mand ArabicShaper ;; Mandaic
; 'mani ArabicShaper ;; Manichaean
; 'phlp ArabicShaper ;; Psalter Pahlavi
;
; 'hang HangulShaper ;; Hangul
;
; 'bali UniversalShaper ;; Balinese
; 'batk UniversalShaper ;; Batak
; 'brah UniversalShaper ;; Brahmi
; 'bugi UniversalShaper ;; Buginese
; 'buhd UniversalShaper ;; Buhid
; 'cakm UniversalShaper ;; Chakma
; 'cham UniversalShaper ;; Cham
; 'dupl UniversalShaper ;; Duployan
; 'egyp UniversalShaper ;; Egyptian Hieroglyphs
; 'gran UniversalShaper ;; Grantha
; 'hano UniversalShaper ;; Hanunoo
; 'java UniversalShaper ;; Javanese
; 'kthi UniversalShaper ;; Kaithi
; 'kali UniversalShaper ;; Kayah Li
; 'khar UniversalShaper ;; Kharoshthi
; 'khoj UniversalShaper ;; Khojki
; 'sind UniversalShaper ;; Khudawadi
; 'lepc UniversalShaper ;; Lepcha
; 'limb UniversalShaper ;; Limbu
; 'mahj UniversalShaper ;; Mahajani
; ';; mand UniversalShaper ;; Mandaic
; ';; mani UniversalShaper ;; Manichaean
; 'mtei UniversalShaper ;; Meitei Mayek
; 'modi UniversalShaper ;; Modi
; ';; mong UniversalShaper ;; Mongolian
; ';; 'nko ' UniversalShaper ;; NKo
; 'hmng UniversalShaper ;; Pahawh Hmong
; ';; phag UniversalShaper ;; Phags-pa
; ';; phlp UniversalShaper ;; Psalter Pahlavi
; 'rjng UniversalShaper ;; Rejang
; 'saur UniversalShaper ;; Saurashtra
; 'shrd UniversalShaper ;; Sharada
; 'sidd UniversalShaper ;; Siddham
; 'sinh UniversalShaper ;; Sinhala
; 'sund UniversalShaper ;; Sundanese
; 'sylo UniversalShaper ;; Syloti Nagri
; 'tglg UniversalShaper ;; Tagalog
; 'tagb UniversalShaper ;; Tagbanwa
; 'tale UniversalShaper ;; Tai Le
; 'lana UniversalShaper ;; Tai Tham
; 'tavt UniversalShaper ;; Tai Viet
; 'takr UniversalShaper ;; Takri
; 'tibt UniversalShaper ;; Tibetan
; 'tfng UniversalShaper ;; Tifinagh
; 'tirh UniversalShaper ;; Tirhuta
'latn DefaultShaper ;; Latin
'DFLT DefaultShaper)) ;; Default
(define (choose script)
(dict-ref SHAPERS script DefaultShaper))

@ -0,0 +1,95 @@
#lang fontkit/racket
(require (prefix-in Script- "script.rkt"))
(provide (all-defined-out))
#|
approximates
https://github.com/mbutterick/fontkit/blob/master/src/opentype/ShapingPlan.js
|#
; * ShapingPlans are used by the OpenType shapers to store which
; * features should by applied, and in what order to apply them.
; * The features are applied in groups called stages. A feature
; * can be applied globally to all glyphs, or locally to only
; * specific glyphs.
(define-subclass object% (ShapingPlan font script language)
(field [direction (Script-direction script)]
[stages empty]
[globalFeatures (mhasheq)]
[allFeatures (mhasheq)])
;; Adds the given features to the last stage.
;; Ignores features that have already been applied.
(define/public (_addFeatures features)
#;(report*/file 'stages-before stages)
(match-define (list head-stages ... last-stage) stages)
(set! stages
`(,@head-stages
,(append last-stage
(for/list ([feature (in-list features)]
#:unless (dict-ref (· this allFeatures) feature #f))
(dict-set! (· this allFeatures) feature #t)
feature))))
#;(report*/file 'stages-after stages)
stages)
;; Adds the given features to the global list
(define/public (_addGlobal features)
(for ([feature (in-list features)])
(dict-set! (· this globalFeatures) feature #t)))
;; Add features to the last stage
(define/public (add arg [global #t])
(when (zero? (length (· this stages)))
(push-end-field! stages this empty))
(when (string? arg)
(set! arg (list arg)))
(cond
[(list? arg)
(_addFeatures arg)
(when global (_addGlobal arg))]
[(dict? arg)
(define features (append (or (· arg global) empty)
(or (· arg local) empty)))
(_addFeatures features)
(when (· arg global)
(_addGlobal (· arg global)))]
[else (raise-argument-error 'ShapingPlan:add "valid arg" arg)]))
;; Add a new stage
(define/public (addStage arg global)
(cond
[(procedure? arg)
(push-end-field! stages this arg)
(push-end-field! stages this empty)]
[else (push-end-field! stages this empty)
(add arg global)]))
;; Assigns the global features to the given glyphs
(define/public (assignGlobalFeatures glyphs)
#;(report*/file glyphs (· this globalFeatures))
(for* ([glyph (in-list glyphs)]
[feature (in-dict-keys (· this globalFeatures))])
(dict-set! (· glyph features) feature #t)))
;; Executes the planned stages using the given OTProcessor
(define/public (process processor glyphs [positions #f])
#;(report*/file 'shaping-plan-process processor)
(send processor selectScript (· this script) (· this language))
#;(report/file stages)
(for/fold ([glyphs glyphs])
([stage (in-list stages)])
(cond
[(and (procedure? stage) (not positions))
(stage (· this font) glyphs positions)]
[(> (length stage) 0)
#;(report*/file 'shaping-plan:applying-features processor)
#;(report/file positions)
#;(report/file (send processor applyFeatures stage glyphs positions))
(send processor applyFeatures stage glyphs positions)]))))

@ -0,0 +1,182 @@
#lang debug fontkit/racket
(require "clone.rkt" "ttfglyphencoder.rkt" "loca.rkt" "directory.rkt" xenomorph)
(provide Subset CFFSubset TTFSubset)
#|
approximates
https://github.com/devongovett/fontkit/blob/master/src/subset/Subset.js
|#
(define-subclass object% (Subset font)
(field [glyphs empty] ; list of glyph ids in the subset
[mapping (mhash)] ; mapping of glyph ids to indexes in `glyphs`
)
(send this includeGlyph 0) ; always include the missing glyph in subset
(define/public (encode-to-port)
(define p (open-output-bytes))
(encode this p)
p)
(as-methods
includeGlyph))
(define/contract (includeGlyph this glyph)
((or/c object? index?) . ->m . index?)
(let ([glyph (if (object? glyph) (· glyph id) glyph)])
(hash-ref! (· this mapping) glyph
(λ ()
;; put the new glyph at the end of `glyphs`,
;; and put its index in the mapping
(push-end-field! glyphs this glyph)
(sub1 (length (· this glyphs)))))))
#|
approximates
https://github.com/mbutterick/fontkit/blob/master/src/subset/CFFSubset.js
|#
(define-subclass Subset (CFFSubset)
#R (· this font)
(field [cff (send (· this font) _getTable 'CFF_)])
(unless (· this cff) (error 'not-a-cff-font))
(field [charStrings #f]
[subrs #f])
(as-methods
subsetCharstrings
#;subsetSubrs
#;subsetFontdict
#;createCIDFontdict
#;addString
#;encode))
(define/contract (subsetCharstrings this)
(->m void?)
(set-field! charStrings this null)
(define gsubrs (make-hash))
(for ([gid (in-list (· this glyphs))])
(push-end-field! charStrings this (· this cff getCharString gid))
(define glyph (· this font getGlyph gid))
(define path (· glyph path)) ; this causes the glyph to be parsed
(for ([subr (in-list (· glyph _usedGsubrs))])
(hash-set! gsubrs subr #true)))
(set-field! this gsubrs (send this subsetSubrs (· this cff globalSubrIndex) gsubrs))
(void))
#|
approximates
https://github.com/mbutterick/fontkit/blob/master/src/subset/TTFSubset.js
|#
(define-subclass Subset (TTFSubset)
(field [glyphEncoder (make-object TTFGlyphEncoder)])
(field [glyf #f]
[offset #f]
[loca #f]
[hmtx #f])
(as-methods
_addGlyph
encode))
(define/contract (_addGlyph this gid)
(index? . ->m . index?)
(define glyph (send (· this font) getGlyph gid))
;; _decode unpacks the `glyf` table data corresponding to a certin gid.
;; here, it's not necessary for non-composite glyphs
;; because they just get copied entirely into the subset.
;; it's just used to detect composite glyphs and handle them specially.
;; so an optimization would be to detect composite / noncomposite without full _decode.
(define glyf (send glyph _decode))
;; get the offset to the glyph from the loca table
(match-define (list curOffset nextOffset) (take (drop (· this font loca offsets) gid) 2))
(define port (send (· this font) _getTableStream 'glyf))
(pos port (+ (pos port) curOffset))
(define buffer (read-bytes (- nextOffset curOffset) port))
;; if it is a compound glyph, include its components
(when (and glyf (negative? (· glyf numberOfContours)))
(for ([component (in-list (· glyf components))])
(define gid (send this includeGlyph (· component glyphID)))
;; note: this (· component pos) is correct. It's a field of a Component object, not a port
(bytes-copy! buffer (· component pos) (send uint16be encode #f gid))))
;; skip variation shit
(push-end-field! glyf this buffer)
(hash-update! (get-field loca this) 'offsets (λ (os)
(append os (list (get-field offset this)))))
(hash-update! (get-field hmtx this) 'metrics (λ (ms) (append ms
(list (mhash 'advance (· glyph advanceWidth)
'bearing (· (send glyph _getMetrics) leftBearing))))))
(increment-field! offset this (bytes-length buffer))
(sub1 (length (· this glyf))))
;; tables required by PDF spec:
;; head, hhea, loca, maxp, cvt, prep, glyf, hmtx, fpgm
;; additional tables required for standalone fonts:
;; name, cmap, OS/2, post
(define/contract (encode this port)
(output-port? . ->m . void?)
(set-field! glyf this empty)
(set-field! offset this 0)
(set-field! loca this (mhash 'offsets empty))
(set-field! hmtx this (mhash 'metrics empty 'bearings empty))
;; include all the glyphs used in the document
;; not using `in-list` because we need to support adding more
;; glyphs to the array as component glyphs are discovered & enqueued
(for ([idx (in-naturals)]
#:break (= idx (length (· this glyphs))))
(define gid (list-ref (· this glyphs) idx))
(send this _addGlyph gid))
(define maxp (cloneDeep (· this font maxp to-hash)))
(dict-set! maxp 'numGlyphs (length (· this glyf)))
;; populate the new loca table
(dict-update! (· this loca) 'offsets (λ (vals) (append vals (list (· this offset)))))
(loca-pre-encode (· this loca))
(define head (cloneDeep (· this font head to-hash)))
(dict-set! head 'indexToLocFormat (· this loca version))
(define hhea (cloneDeep (· this font hhea to-hash)))
(dict-set! hhea 'numberOfMetrics (length (· this hmtx metrics)))
(send Directory encode port
(mhash 'tables
(mhash
'head head
'hhea hhea
'loca (· this loca)
'maxp maxp
'cvt_ (· this font cvt_)
'prep (· this font prep)
'glyf (· this glyf)
'hmtx (· this hmtx)
'fpgm (· this font fpgm))))
#;(report* (bytes-length (send stream dump)) (send stream dump))
#;(report* (bytes-length (file->bytes "out.bin")) (file->bytes "out.bin"))
(void)
)

Binary file not shown.

@ -0,0 +1,26 @@
#lang fontkit/racket
(provide (all-defined-out))
(require (for-syntax racket/string))
#|
approximates
https://github.com/mbutterick/fontkit/blob/master/src/tables/index.js
|#
(define-macro (define-table-codecs ID TABLE-ID ...)
(with-pattern ([(TABLE-ID-STRING ...) (pattern-case-filter #'(TABLE-ID ...)
[STX (datum->syntax caller-stx (string-replace (format "~a.rkt" (syntax->datum #'STX)) "/" ""))])])
#'(begin
(r+p TABLE-ID-STRING ...)
(test-module (require (submod TABLE-ID-STRING test) ...))
(define ID (make-hasheq (map cons (list 'TABLE-ID ...) (list TABLE-ID ...)))))))
(define-table-codecs table-codecs
;; required tables
#;cmap head hhea hmtx maxp #;name OS/2 post
;; TrueType outlines
cvt_ fpgm loca prep glyf
;; PostScript outlines
CFF_ CFF2 #;VORG
;; Advanced OpenType Tables
#;BASE #;GDEF GPOS GSUB #;JSTF)

@ -0,0 +1,179 @@
#lang fontkit/racket
(require "glyph.rkt" xenomorph)
(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-subclass Struct (RGlyfHeader))
(define GlyfHeader (+RGlyfHeader
(dictify 'numberOfContours int16be ;; if negative, this is a composite glyph
'xMin int16be
'yMin int16be
'xMax int16be
'yMax int16be)))
;; Flags for simple glyphs
(define-macro (define-flag-series . IDS)
#`(match-define (list . IDS) (map (curry expt 2) (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
(define-subclass object% (Point onCurve endContour [x 0] [y 0])
(define/public (copy)
(+Point onCurve endContour x y)))
;; Represents a component in a composite glyph
(define-subclass object% (Component glyphID dx dy)
(field [pos 0]
[scaleX 1]
[scaleY 1]
[scale01 0]
[scale10 0]))
;; Represents a TrueType glyph.
(define-subclass Glyph (TTFGlyph)
(inherit-field _font id)
;; Parses just the glyph header and returns the bounding box
(define/override (_getCBox internal)
(unfinished))
;; Parses a single glyph coordinate
(define/public (_parseGlyphCoord port prev short same)
(unless (input-port? port)
(raise-argument-error '_parseGlyphCoord "input port" port))
(unless (number? prev)
(raise-argument-error '_parseGlyphCoord "number" prev))
(unless (and (boolean? short) (boolean? same))
(raise-argument-error '_parseGlyphCoord "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
(define/public (_decode)
(define offsets (· _font loca offsets))
(match-define (list glyfPos nextPos) (take (drop offsets id) 2))
;; Nothing to do if there is no data for this glyph
(and (not (= glyfPos nextPos))
(let ()
(define port (send _font _getTableStream 'glyf))
(pos port (+ (pos port) glyfPos))
(define startPos (pos port))
(define glyph (decode GlyfHeader port))
(match (· glyph numberOfContours)
[(? positive?) (_decodeSimple glyph port)]
[(? negative?) (_decodeComposite glyph port startPos)])
glyph)))
(define/public (_decodeSimple glyph port)
(unless (dict? glyph)
(raise-argument-error 'TTFGlyph-_decodeSimple "decoded RGlyfHeader" glyph))
(unless (input-port? port)
(raise-argument-error 'TTFGlyph-_decodeSimple "input port" port))
;; this is a simple glyph
(dict-set! glyph 'points empty)
(define endPtsOfContours (decode (+Array uint16be (· glyph numberOfContours)) port))
(dict-set! glyph 'instructions (decode (+Array uint8be uint16be) port))
(define numCoords (add1 (last endPtsOfContours)))
(define flags
(for*/lists (flags)
([i (in-naturals)]
#:break (= (length flags) numCoords)
[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 (+Point (zero? (bitwise-and flag ON_CURVE)) (and (index-of endPtsOfContours i) #t) 0 0))
(define next-px (_parseGlyphCoord port px (not (zero? (bitwise-and flag X_SHORT_VECTOR))) (not (zero? (bitwise-and flag SAME_X)))))
(define next-py (_parseGlyphCoord port py (not (zero? (bitwise-and flag Y_SHORT_VECTOR))) (not (zero? (bitwise-and flag SAME_Y)))))
(set-field! x point next-px)
(set-field! y point next-py)
(values (cons point points) next-px next-py)))
(dict-set! glyph 'points (reverse points)))
(define/public (_decodeComposite glyph port [offset 0])
;; this is a composite glyph
(dict-set! glyph 'components empty)
(define haveInstructions #f)
(define flags MORE_COMPONENTS)
(dict-set! glyph 'components
(for/list ([i (in-naturals)]
#:break (zero? (bitwise-and flags MORE_COMPONENTS)))
(set! flags (send uint16be decode port))
(define gPos (- (pos port) offset))
(define glyphID (send uint16be decode 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 (send decoder decode port) (send decoder decode port))))
(define component (+Component glyphID dx dy))
(set-field! pos component gPos)
(cond
[(not (zero? (bitwise-and flags WE_HAVE_A_SCALE)))
(define scale (read-fixed14 port))
(set-field! scaleX component scale)
(set-field! scaleY component scale)]
[(not (zero? (bitwise-and flags WE_HAVE_AN_X_AND_Y_SCALE)))
(set-field! scaleX component (read-fixed14 port))
(set-field! scaleY component (read-fixed14 port))]
[(not (zero? (bitwise-and flags WE_HAVE_A_TWO_BY_TWO)))
(set-field! scaleX component (read-fixed14 port))
(set-field! scale01 component (read-fixed14 port))
(set-field! scale10 component (read-fixed14 port))
(set-field! scaleY 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 (send uint8 decode stream))
(define b2 (send uint8 decode stream))
(bytes->fixed14 b1 b2))

@ -0,0 +1,11 @@
#lang fontkit/racket
(provide TTFGlyphEncoder)
(define-subclass object% (TTFGlyphEncoder)
(as-methods
encodeSimple
_encodePoint))
(define-stub encodeSimple)
(define-stub _encodePoint)

@ -0,0 +1,3 @@
#lang info
(define collection 'multi)
(define version "0.0")
Loading…
Cancel
Save