You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
typesetting/fontland/fontland/subset.rkt

201 lines
6.8 KiB
Racket

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

#lang debug racket/base
(require racket/serialize
racket/contract
racket/class
racket/list
racket/match
sugar/unstable/class
sugar/unstable/dict
sugar/unstable/js
"table/loca.rkt"
"directory.rkt"
"helper.rkt"
fontland/glyph
fontland/ttf-glyph
xenomorph)
(provide subset +subset ttf-subset +ttf-subset subset-include-glyph encode-to-port)
#|
approximates
https://github.com/devongovett/fontkit/blob/master/src/subset/Subset.js
|#
; glyphs = list of glyph ids in the subset
; mapping = of glyph ids to indexes in `glyphs`
(struct subset (font glyphs mapping) #:transparent #:mutable)
(define (+subset font [glyphs empty] [mapping (mhash)])
(define ss (subset font glyphs mapping))
(subset-include-glyph ss 0)
ss)
(define (encode-to-port ss)
(define p (open-output-bytes))
(encode ss p)
p)
(define (subset-include-glyph ss glyph-or-gid)
#;((or/c object? index?) . ->m . index?)
(define glyph (if (object? glyph-or-gid)
(· glyph-or-gid id)
glyph-or-gid))
(hash-ref! (subset-mapping ss) glyph
(λ ()
;; put the new glyph at the end of `glyphs`,
;; and put its index in the mapping
(set-subset-glyphs! ss (append (subset-glyphs ss) (list glyph)))
(sub1 (length (subset-glyphs ss))))))
#|
approximates
https://github.com/mbutterick/fontkit/blob/master/src/subset/CFFSubset.js
|#
#|
;; no CFF font support for now
(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
|#
(struct ttf-subset subset (glyf offset loca hmtx) #:transparent #:mutable)
(define (+ttf-subset font [glyphs empty] [mapping (mhash)]
[glyf #f]
[offset #f]
[loca #f]
[hmtx #f])
(define ss (ttf-subset font glyphs mapping glyf offset loca hmtx))
(subset-include-glyph ss 0)
ss)
(define (ttf-subset-add-glyph ss gid)
#;(index? . ->m . index?)
(define glyph (send (subset-font ss) getGlyph gid))
;; glyph-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 glyph-decode.
(define ttf-glyf-data (glyph-decode glyph))
;; get the offset to the glyph from the loca table
(match-define (list curOffset nextOffset) (take (drop (· (subset-font ss) loca offsets) gid) 2))
(define port (send (subset-font ss) _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 ttf-glyf-data (negative? (· ttf-glyf-data numberOfContours)))
(for ([ttf-glyph-component (in-list (· ttf-glyf-data components))])
(define gid (subset-include-glyph ss (ttf-glyph-component-glyph-id ttf-glyph-component)))
;; note: this (ttf-glyph-component-pos component) is correct. It's a field of a Component object, not a port
(bytes-copy! buffer (ttf-glyph-component-pos ttf-glyph-component) (send uint16be encode #f gid))))
;; skip variation shit
(set-ttf-subset-glyf! ss (append (ttf-subset-glyf ss) (list buffer)))
(hash-update! (ttf-subset-loca ss) 'offsets (λ (os)
(append os (list (ttf-subset-offset ss)))))
(hash-update! (ttf-subset-hmtx ss) 'metrics (λ (ms) (append ms
(list (mhash 'advance (glyph-advance-width glyph)
'bearing (· (get-glyph-metrics glyph) leftBearing))))))
(set-ttf-subset-offset! ss (+ (ttf-subset-offset ss) (bytes-length buffer)))
(sub1 (length (ttf-subset-glyf ss))))
;; 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 (clone-deep val) (deserialize (serialize val)))
(define (encode ss port)
#;(output-port? . ->m . void?)
(set-ttf-subset-glyf! ss empty)
(set-ttf-subset-offset! ss 0)
(set-ttf-subset-loca! ss (mhash 'offsets empty))
(set-ttf-subset-hmtx! ss (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 (subset-glyphs ss))))
(define gid (list-ref (subset-glyphs ss) idx))
(ttf-subset-add-glyph ss gid))
(define maxp (clone-deep (· (subset-font ss) maxp to-hash)))
(dict-set! maxp 'numGlyphs (length (ttf-subset-glyf ss)))
;; populate the new loca table
(dict-update! (ttf-subset-loca ss) 'offsets (λ (vals) (append vals (list (ttf-subset-offset ss)))))
(loca-pre-encode (ttf-subset-loca ss))
(define head (clone-deep (· (subset-font ss) head to-hash)))
(dict-set! head 'indexToLocFormat (· (ttf-subset-loca ss) version))
(define hhea (clone-deep (· (subset-font ss) hhea to-hash)))
(dict-set! hhea 'numberOfMetrics (length (· (ttf-subset-hmtx ss) metrics)))
(send Directory encode port
(mhash 'tables
(mhash
'head head
'hhea hhea
'loca (ttf-subset-loca ss)
'maxp maxp
'cvt_ (· (subset-font ss) cvt_)
'prep (· (subset-font ss) prep)
'glyf (ttf-subset-glyf ss)
'hmtx (ttf-subset-hmtx ss)
'fpgm (· (subset-font ss) fpgm))))
#;(report* (bytes-length (send stream dump)) (send stream dump))
#;(report* (bytes-length (file->bytes "out.bin")) (file->bytes "out.bin"))
(void)
)