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

308 lines
12 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/list
racket/match
sugar/unstable/dict
"table/loca.rkt"
"table-stream.rkt"
"directory.rkt"
"struct.rkt"
fontland/glyph
fontland/ttf-glyph
fontland/cff-glyph
xenomorph
racket/dict
fontland/table/cff/cff-font
fontland/table/cff/cff-top
fontland/table/cff/cff-standard-strings)
(provide subset +subset
ttf-subset +ttf-subset ttf-subset?
cff-subset +cff-subset cff-subset?
subset-add-glyph! encode-to-port create-subset)
#|
from
https://github.com/mbutterick/fontkit/blob/master/src/TTFFont.js
|#
(define (create-subset font)
((if (has-table? font #"CFF_")
+cff-subset
+ttf-subset) font))
#|
approximates
https://github.com/mbutterick/fontkit/blob/master/src/subset/Subset.js
|#
(define (+subset font [glyphs empty] [mapping (mhash)])
(define ss (subset font glyphs mapping))
(subset-add-glyph! ss 0)
ss)
(define (encode-to-port ss)
(define p (open-output-bytes))
((if (cff-subset? ss)
cff-subset-encode
ttf-subset-encode) ss p)
p)
(define (subset-add-glyph! ss glyph-or-gid) ; fka `includeGlyph`
(define new-gid ((if (glyph? glyph-or-gid) glyph-id values) glyph-or-gid))
;; put the new glyph at the end of `glyphs`,
;; and put its index in the mapping
(hash-ref! (subset-mapping ss) new-gid
(λ ()
(set-subset-glyphs! ss (append (subset-glyphs ss) (list new-gid)))
(sub1 (length (subset-glyphs ss))))))
#|
approximates
https://github.com/mbutterick/fontkit/blob/master/src/subset/CFFSubset.js
|#
(define (+cff-subset font [glyphs empty] [mapping (mhash)]
[cff (get-table font 'CFF_)]
[strings #f]
[charstrings #f]
[gsubrs #f])
(define ss (cff-subset font glyphs mapping cff strings charstrings gsubrs))
(subset-add-glyph! ss 0)
ss)
(require racket/format racket/string)
(define (bytes->hexes bs)
(string-join
(for/list ([b (in-bytes bs)])
(~r #:base 16 b #:min-width 2 #:pad-string "0"))  " "))
(define (subset-charstrings this)
(set-cff-subset-charstrings! this null)
(define gsubrs (make-hasheq))
(for ([gid (in-list (subset-glyphs this))])
(set-cff-subset-charstrings!
this
(append (cff-subset-charstrings this)
(list (get-char-string (cff-subset-cff this) gid))))
(define glyph (get-glyph (subset-font this) gid))
(unless (cff-glyph-path glyph) (getPath glyph)) ;; this causes the glyph to be parsed
(for ([subr (in-hash-keys (cff-glyph-_usedGsubrs glyph))])
(hash-set! gsubrs subr #true)))
(set-cff-subset-gsubrs! this (subset-subrs
this
(hash-ref (cff-subset-cff this) 'globalSubrIndex)
gsubrs)))
(define (subset-subrs this subrs used)
(for/vector ([(subr i) (in-indexed subrs)])
(cond
[(hash-ref used i #false)
(pos (hash-ref (cff-subset-cff this) 'stream) (index-item-offset subr))
(read-bytes (index-item-length subr) (hash-ref (cff-subset-cff this) 'stream))]
[else (bytes 11)])))
(define (subset-font-dict this topDict)
(error 'subsetFontdict-unimplemented))
(define (create-cid-fontdict this top-dict)
(define used-subrs (make-hasheq))
(for ([gid (in-list (subset-glyphs this))])
(define glyph (get-glyph (subset-font this) gid))
(unless (cff-glyph-path glyph) (getPath glyph)) ;; this causes the glyph to be parsed
(for ([subr (in-hash-keys (cff-glyph-_usedSubrs glyph))])
(hash-set! used-subrs subr #true)))
(define cff-topDict (hash-ref (cff-subset-cff this) 'topDict))
(define private-dict (hash-copy (hash-ref cff-topDict 'Private (make-hasheq))))
(when (and (hash-has-key? cff-topDict 'Private) (hash-has-key? (hash-ref cff-topDict 'Private) 'Subrs))
(hash-set! private-dict 'Subrs (subset-subrs this
(hash-ref (hash-ref cff-topDict 'Private) 'Subrs)
used-subrs)))
(hash-set! top-dict 'FDArray (list (dictify 'Private private-dict)))
(hash-set! top-dict 'FDSelect (dictify 'version 3
'nRanges 1
'ranges (list (dictify 'first 0 'fd 0))
'sentinel (length (cff-subset-charstrings this))))
(hash-ref top-dict 'FDSelect))
(define (add-string this [string #f])
(cond
[(not string) #false]
[else
(unless (cff-subset-strings this)
(set-cff-subset-strings! this null))
(set-cff-subset-strings! this
(append (cff-subset-strings this) (list string)))
(+ (vector-length standard-strings) (sub1 (length (cff-subset-strings this))))]))
(define (cff-subset-encode this stream)
(subset-charstrings this)
(define charset
(dictify 'version (if (> (length (cff-subset-charstrings this)) 255) 2 1)
'ranges (list (dictify 'first 1 'nLeft (- (length (cff-subset-charstrings this)) 2)))))
(define top-dict (hash-copy (hash-ref (cff-subset-cff this) 'topDict)))
(hash-set*! top-dict
'Private #false
'charset charset
'Encoding #false
'CharStrings (cff-subset-charstrings this))
(for ([key (in-list '(version Notice Copyright FullName
FamilyName Weight PostScript
BaseFontName FontName))])
(hash-update! top-dict key
(λ (tdk-val) (add-string this (CFFont-string (cff-subset-cff this) tdk-val)))))
(hash-set! top-dict 'ROS (list (add-string this "Adobe") (add-string this "Identity") 0))
(hash-set! top-dict 'CIDCount (length (cff-subset-charstrings this)))
(if (hash-ref (cff-subset-cff this) 'isCIDFont)
(subset-font-dict this top-dict)
(create-cid-fontdict this top-dict))
(define top
(mhasheq 'version 1
'hdrSize (hash-ref (cff-subset-cff this) 'hdrSize)
'offSize 4
'header (hash-ref (cff-subset-cff this) 'header #f)
'nameIndex (list (CFFFont-postscriptName (cff-subset-cff this)))
'topDictIndex (list top-dict)
'stringIndex (cff-subset-strings this)
'globalSubrIndex (cff-subset-gsubrs this)))
(for ([k (in-list (sort (dict-keys top-dict) symbol<?))])
(match (dict-ref top-dict k)
[(or (? list? (? dict?))) k]
[val val]))
(encode CFFTop top stream))
#;(module+ test
(require "font.rkt" "helper.rkt")
(define otf (open-font (path->string fira-otf-path)))
(define cffss (+cff-subset otf))
cffss)
#|
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-add-glyph! ss 0)
ss)
(define (ttf-subset-add-glyph ss 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 glyph (get-glyph (subset-font ss) gid))
(define ttf-glyf-data (glyph-decode glyph))
;; get the offset to the glyph from the loca table
(match-define (list this-offset next-offset)
(take (drop (hash-ref (get-table (subset-font ss) 'loca) 'offsets) gid) 2))
(define port (get-table-stream (subset-font ss) 'glyf))
(pos port (+ (pos port) this-offset))
(define glyf-bytes (read-bytes (- next-offset this-offset) port))
;; if it is a compound glyph, include its components
(when (and ttf-glyf-data (negative? (hash-ref ttf-glyf-data 'numberOfContours)))
(for ([ttf-glyph-component (in-list (hash-ref ttf-glyf-data 'components))])
(define gid (subset-add-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! glyf-bytes (ttf-glyph-component-pos ttf-glyph-component) (encode uint16be gid #f))))
;; `loca` table v0 stores offsets as half of actual value
;; so we need an even number of bytes to encode
(define glyf-bytes-even (if (odd? (bytes-length glyf-bytes))
(bytes-append glyf-bytes #"0")
glyf-bytes))
(set-ttf-subset-glyf! ss (append (ttf-subset-glyf ss) (list glyf-bytes-even)))
(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 (hash-ref (get-glyph-metrics glyph) 'leftBearing))))))
(set-ttf-subset-offset! ss (+ (ttf-subset-offset ss) (bytes-length glyf-bytes-even)))
(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 (ttf-subset-encode ss port)
(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 new-maxp-table (clone-deep (get-maxp-table (subset-font ss))))
(dict-set! new-maxp-table '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 new-head-table (clone-deep (get-head-table (subset-font ss))))
(dict-set! new-head-table 'indexToLocFormat (dict-ref (ttf-subset-loca ss) x:version-key))
(define new-hhea-table (clone-deep (get-hhea-table (subset-font ss))))
(dict-set! new-hhea-table 'numberOfMetrics (length (dict-ref (ttf-subset-hmtx ss) 'metrics)))
(define new-tables
(filter cdr (dictify 'head new-head-table
'hhea new-hhea-table
'loca (ttf-subset-loca ss)
'maxp new-maxp-table
'cvt_ (and (has-table? (subset-font ss) 'cvt_)
(get-cvt_-table (subset-font ss)))
'prep (and (has-table? (subset-font ss) 'prep)
(get-prep-table (subset-font ss)))
'glyf (ttf-subset-glyf ss)
'hmtx (ttf-subset-hmtx ss)
'fpgm (and (has-table? (subset-font ss) 'fpgm)
(get-fpgm-table (subset-font ss))))))
(encode directory (mhash 'tables new-tables) port)
(void))