main
Matthew Butterick 8 years ago
parent bf16a9f31d
commit 47a5fed368

@ -163,9 +163,6 @@
(hash-set! (· this _info data) key (if (string? val) (String val) val)))
(· this _info end)
;; todo: fonts
;; for name, font of @_fontFamilies
;; font.finalize()
(for ([font (in-hash-values (· this _fontFamilies))])
(· font finalize))
@ -183,31 +180,31 @@
(() ((or/c procedure? #f)) . ->*m . void?)
;; generate xref
(define xref-offset (· this _offset))
(_write this "xref")
(_write this (format "0 ~a" (add1 (length (· this _offsets)))))
(_write this "0000000000 65535 f ")
(for ([offset (in-list (· this _offsets))])
(_write this (string-append
(~r offset #:min-width 10 #:pad-string "0")
" 00000 n ")))
;; trailer
(_write this "trailer")
(_write this (convert
(mhash 'Size (add1 (length (· this _offsets)))
'Root (· this _root)
'Info (· this _info))))
(_write this "startxref")
(_write this (number xref-offset))
(_write this "%%EOF")
(with-method ([this-write (this _write)])
(define this-offsets (· this _offsets))
(this-write "xref")
(this-write (format "0 ~a" (add1 (length this-offsets))))
(this-write "0000000000 65535 f ")
(for ([offset (in-list this-offsets)])
(this-write (string-append
(~r offset #:min-width 10 #:pad-string "0")
" 00000 n ")))
(this-write "trailer") ;; trailer
(this-write (convert
(mhash 'Size (add1 (length this-offsets))
'Root (· this _root)
'Info (· this _info))))
(this-write "startxref")
(this-write (number xref-offset))
(this-write "%%EOF"))
;; end the stream
;; in node you (@push null) which signals to the stream
;; to copy to its output port
;; here we'll do it manually
(copy-port (open-input-bytes (apply bytes-append (reverse (· this byte-strings)))) (· this op))
(close-output-port (· this op)))
(define this-op (· this op))
(copy-port (open-input-bytes (apply bytes-append (reverse (· this byte-strings)))) this-op)
(close-output-port this-op))
(module+ test

@ -37,14 +37,15 @@
(class PDFFont
(super-new)
(init-field document name id)
(field [font (make-object AFMFont ((hash-ref STANDARD_FONTS name)))]
(field [font (make-object AFMFont ((hash-ref standard-fonts name)))]
[ascender (· font ascender)]
[descender (· font descender)]
[bbox (· font bbox)]
[lineGap (· font lineGap)])
(as-methods
embed
encode)))
encode
widthOfString)))
(define/contract (embed this)
(->m void?)
@ -73,5 +74,15 @@
(list encoded positions))
(define/contract (widthOfString this str size [options #f])
((string? number?) ((or/c hash? #f)) . ->*m . number?)
(let* ([this-font (· this font)]
[glyphs (send this-font glyphsForString str)]
[advances (send this-font advancesForGlyphs glyphs)]
[width (apply + advances)]
[scale (/ size 1000)])
(* width scale)))
(module+ test
(define stdfont (make-object StandardFont #f "Helvetica" #f)))

@ -5,7 +5,7 @@
(class object%
(super-new)
(init-field contents)
(field [attributes (mhash)]
(field [attributes (mhasheq)]
[glyphWidths (mhash)]
[boundingBoxes (mhash)]
[kernPairs (mhash)])
@ -14,12 +14,11 @@
(field [charWidths (for/list ([i (in-range 256)])
(hash-ref glyphWidths (vector-ref characters i) #f))])
(field [bbox (for/list ([attr (in-list (string-split (hash-ref attributes "FontBBox")))])
(field [bbox (for/list ([attr (in-list (string-split (hash-ref attributes 'FontBBox)))])
(or (string->number attr) 0))])
(field [ascender (string->number (or (hash-ref attributes "Ascender" #f) "0"))])
(field [descender (string->number (or (hash-ref attributes "Descender" #f) "0"))])
(field [lineGap (- (- (list-ref bbox 3) (list-ref bbox 1))
(- ascender descender))])
(field [ascender (string->number (or (hash-ref attributes 'Ascender #f) "0"))])
(field [descender (string->number (or (hash-ref attributes 'Descender #f) "0"))])
(field [lineGap (- (- (list-ref bbox 3) (list-ref bbox 1)) (- ascender descender))])
(as-methods
parse
@ -37,21 +36,19 @@
(define/contract (parse this)
(->m void?)
(define section #f)
(for ([line (in-list (string-split (· this contents) "\n"))])
;; `section` preserves state during the loop
(cond
[(regexp-match #px"^Start(\\w+)" line)
=> (λ (match) (set! section (cadr match)))]
[(regexp-match #px"^End(\\w+)" line) (set! section #f)])
(case section
(for*/fold ([last-section #f])
([line (in-list (string-split (· this contents) "\n"))])
(define current-section (cond
[(regexp-match #px"^Start(\\w+)" line) => (λ (match) (cadr match))]
[(regexp-match #px"^End(\\w+)" line) #f]
[else last-section]))
(case current-section
[("FontMetrics")
;; line looks like this:
;; FontName Helvetica
;; key space value. Possibly multiple lines with same key.
(match-define (list _ key value) (regexp-match #px"^(\\w+)\\s+(.*)" line))
(hash-update! (· this attributes) key
(hash-update! (· this attributes) (string->symbol key)
(λ (v) (if (equal? v value)
value
(append (if (pair? v) v (list v)) (list value)))) value)]
@ -68,61 +65,60 @@
[("KernPairs")
(when (string-prefix? line "KPX")
(match-define (list _ left right val) (string-split line))
(hash-set! (· this kernPairs) (make-kern-table-key left right) (string->number val)))]))
)
(hash-set! (· this kernPairs) (make-kern-table-key left right) (string->number val)))])
current-section)
(void))
(define (make-kern-table-key left right)
(cons left right))
(define win-ansi-table
(hash 402 131
8211 150
8212 151
8216 145
8217 146
8218 130
8220 147
8221 148
8222 132
8224 134
8225 135
8226 149
8230 133
8364 128
8240 137
8249 139
8250 155
710 136
8482 153
338 140
339 156
732 152
352 138
353 154
376 159
381 142
382 158))
(define/contract (encodeText this text)
(hasheqv 402 131
8211 150
8212 151
8216 145
8217 146
8218 130
8220 147
8221 148
8222 132
8224 134
8225 135
8226 149
8230 133
8364 128
8240 137
8249 139
8250 155
710 136
8482 153
338 140
339 156
732 152
352 138
353 154
376 159
381 142
382 158))
(define/contract (encodeText this str)
(string? . ->m . (listof string?))
(for/list ([c (in-string text)])
(for/list ([c (in-string str)])
(define cint (char->integer c))
(number->string (hash-ref win-ansi-table cint cint) 16)))
(define/contract (glyphsForString this string)
(define/contract (glyphsForString this str)
(string? . ->m . (listof any/c))
(for/list ([c (in-string string)])
(define charCode (char->integer c))
(send this characterToGlyph charCode)))
(for/list ([c (in-string str)])
(send this characterToGlyph (char->integer c))))
(define/contract (characterToGlyph this character)
(define/contract (characterToGlyph this cint)
(integer? . ->m . any)
(define idx (hash-ref win-ansi-table character character))
(if (< idx (vector-length characters))
(vector-ref characters idx)
".notdef"))
(define idx (hash-ref win-ansi-table cint cint))
(vector-ref characters (if (< idx (vector-length characters)) idx 0)))
(define/contract (widthOfGlyph this glyph)
@ -139,8 +135,7 @@
((listof any/c) . ->m . (listof number?))
(for/list ([left (in-list glyphs)]
[right (in-list (append (cdr glyphs) (list #\nul)))])
(+ (send this widthOfGlyph left)
(send this getKernPair left right))))
(+ (send this widthOfGlyph left) (send this getKernPair left right))))
(define characters (list->vector (string-split @string-append{

@ -1,13 +1,13 @@
#lang pitfall/racket
(require racket/runtime-path)
(provide isStandardFont STANDARD_FONTS)
(provide isStandardFont standard-fonts)
(define (isStandardFont name)
(hash-ref STANDARD_FONTS name #f))
(hash-ref standard-fonts name #f))
(define-runtime-path Helvetica "data/Helvetica.afm")
(define STANDARD_FONTS
(define standard-fonts
(hash "Helvetica" (λ () (file->string Helvetica))))
(module+ test

@ -35,8 +35,7 @@
(cond
#;[(is-a? color PDFGradient) color] ; todo
;; 3-digit hex becomes 6-digit hex
[(and (string? color)
(regexp-match #px"^#(?i:[0-9A-F]){3}$" color))
[(and (string? color) (regexp-match #px"^#(?i:[0-9A-F]){3}$" color))
(loop (list->string (cdr (apply append
(for/list ([c (in-string color)])
(list c c))))))] ; change #abc to ##aabbcc then drop the first char
@ -115,27 +114,24 @@
this)
(define/contract (_doOpacity this fillOpacity strokeOpacity)
((or/c number? #f) (or/c number? #f) . ->m . object?)
(when (or fillOpacity strokeOpacity)
(set! fillOpacity (and fillOpacity (bounded 0 fillOpacity 1)))
(set! strokeOpacity (and strokeOpacity (bounded 0 strokeOpacity 1)))
(define/contract (_doOpacity this [fill-arg #f] [stroke-arg #f])
(() ((or/c number? #f) (or/c number? #f)) . ->*m . object?)
(define fill-opacity (and fill-arg (bounded 0 fill-arg 1)))
(define stroke-opacity (and stroke-arg (bounded 0 stroke-arg 1)))
(when (or fill-opacity stroke-opacity)
(define key (format "~a_~a"
(if fillOpacity (number fillOpacity) "")
(if strokeOpacity (number strokeOpacity) "")))
(if fill-opacity (number fill-opacity) "")
(if stroke-opacity (number stroke-opacity) "")))
(match-define (list dictionary name)
(hash-ref! (get-field _opacityRegistry this) key
(λ ()
(define dictionary (mhash 'Type "ExtGState"))
(when fillOpacity
(hash-set! dictionary 'ca fillOpacity))
(when strokeOpacity
(hash-set! dictionary 'CA strokeOpacity))
(define dict-ref (send this ref dictionary))
(· dict-ref end)
(list dict-ref (format "Gs~a" (increment-field! _opacityCount this))))))
(when fill-opacity (hash-set! dictionary 'ca fill-opacity))
(when stroke-opacity (hash-set! dictionary 'CA stroke-opacity))
(define ref-dict (send this ref dictionary))
(· ref-dict end)
(list ref-dict (format "Gs~a" (increment-field! _opacityCount this))))))
(hash-set! (· this page ext_gstates) name dictionary)
(send this addContent (format "/~a gs" name))))

@ -38,20 +38,19 @@
(define/contract (font this src [size-or-family #f] [maybe-size #f])
((any/c) ((or/c string? number? #f) (or/c number? #f)) . ->*m . object?)
(define-values (family size) (if (number? size-or-family)
(values #f size-or-family)
(values size-or-family maybe-size)))
(match-define (list family size) (if (number? size-or-family)
(list #f size-or-family)
(list size-or-family maybe-size)))
;; check registered fonts if src is a string
(define cacheKey #f)
(cond
[(and (string? src) (hash-ref (· this _registeredFonts) src #f))
(set! cacheKey src)
(set! src (hash-ref (hash-ref (· this _registeredFonts) src) src #f))
(set! family (hash-ref (hash-ref (· this _registeredFonts) src) family #f))]
[else
(set! cacheKey (or family src))
(set! cacheKey (if (string? cacheKey) cacheKey #f))])
(define cacheKey (let ([this-rfs (· this _registeredFonts)])
(cond
[(and (string? src) (hash-ref this-rfs src #f))
(define ck src)
(set! src (hash-ref (hash-ref this-rfs src) src #f))
(set! family (hash-ref (hash-ref this-rfs src) family #f))
ck]
[else (let ([ck (or family src)])
(and (string? ck) ck))])))
(when size (set-field! fontSize this size))

@ -43,24 +43,20 @@
(define/contract (_text this text x y options lineCallback)
(string? (or/c number? #f) (or/c number? #f) hash? procedure? . ->m . object?)
(set! options (send this _initOptions options x y))
;; Convert text to a string
;; q: what else might it be?
(set! text (format "~a" text))
;; if the wordSpacing option is specified, remove multiple consecutive spaces
(when (hash-ref options 'wordSpacing #f)
(set! text (string-replace text #px"\\s{2,}" " ")))
;; word wrapping
(cond
#;[(hash-ref options 'width #f)
] ; todo
[else ; render paragraphs as single lines
(for ([line (in-list (string-split text "\n"))])
(lineCallback line options))])
(let* ([options (send this _initOptions options x y)]
[text (format "~a" text)] ;; Convert text to a string
;; if the wordSpacing option is specified, remove multiple consecutive spaces
[text (if (hash-ref options 'wordSpacing #f)
(string-replace text #px"\\s{2,}" " ")
text)])
;; word wrapping
(cond
#;[(hash-ref options 'width #f) (error 'unimplemented-branch-of-_text)] ; todo
[else ; render paragraphs as single lines
(for ([line (in-list (string-split text "\n"))])
(lineCallback line options))]))
this)
@ -69,37 +65,37 @@
(send this _text text-string x y options (curry _line this)))
(define/contract (widthOfString this string [options (mhash)])
(define/contract (widthOfString this str [options (mhash)])
((string?) (hash?) . ->*m . number?)
42 ; todo
)
(+ (send (· this _font) widthOfString str (· this _fontSize) (hash-ref options 'features #f))
(* (hash-ref options 'characterSpacing 0) (sub1 (string-length str)))))
(define/contract (_initOptions this [options (mhash)] [x #f] [y #f])
(() (hash? (or/c number? #f) (or/c number? #f)) . ->*m . hash?)
;; clone options object
(set! options (hash-copy options))
(let ([options (hash-copy options)])
;; extend options with previous values for continued text
(when (· this _textOptions)
(for ([(key val) (in-hash (· this _textOptions))]
#:unless (equal? (key "continued")))
(hash-ref! options key val)))
;; extend options with previous values for continued text
(when (· this _textOptions)
(for ([(key val) (in-hash (· this _textOptions))]
#:unless (equal? (key "continued")))
(hash-ref! options key val)))
;; Update the current position
(when x (set-field! x this x))
(when y (set-field! y this y))
;; Update the current position
(when x (set-field! x this x))
(when y (set-field! y this y))
;; wrap to margins if no x or y position passed
(unless (not (hash-ref options 'lineBreak #t))
(define margins (· this page margins))
(hash-ref! options 'width (λ () (- (· this page width) (· this x) (· margins right)))))
;; wrap to margins if no x or y position passed
(unless (not (hash-ref options 'lineBreak #t))
(define margins (· this page margins))
(hash-ref! options 'width (λ () (- (· this page width) (· this x) (· margins right)))))
(hash-ref! options 'columns 0)
(hash-ref! options 'columnGap 18) ; 1/4 inch in PS points
(hash-ref! options 'columns 0)
(hash-ref! options 'columnGap 18) ; 1/4 inch in PS points
options)
options))
(define/contract (_line this text [options (mhash)] [wrapper #f])
@ -112,7 +108,7 @@
(void))
(define/contract (_fragment this text x y options)
(define/contract (_fragment this text x y-in options)
(string? number? number? hash? . ->m . void?)
(define align (hash-ref options 'align 'left))
@ -130,10 +126,10 @@
;; flip coordinate system
(send this save)
(send this transform 1 0 0 -1 0 (· this page height))
(set! y (- (· this page height) y (* (/ (· this _font ascender) 1000) (· this _fontSize))))
(define y (- (· this page height) y-in (* (/ (· this _font ascender) 1000) (· this _fontSize))))
;; add current font to page if necessary
(hash-ref! (· this page fonts) (· this _font id) (λ () "a font ref" (· this _font ref)))
(hash-ref! (· this page fonts) (· this _font id) (λ () (· this _font ref)))
;; begin the text object
(send this addContent "BT")
@ -145,15 +141,15 @@
(send this addContent (format "/~a ~a Tf" (· this _font id) (number (· this _fontSize))))
;; rendering mode
(define mode (cond
[(and (hash-ref options 'fill #f) (hash-ref options 'stroke #f)) 2]
[(hash-ref options 'stroke #f) 1]
[else 0]))
(when (and mode (not (zero? mode)))
(send this addContent (format "~a Tr" mode)))
(let ([mode (cond
[(and (hash-ref options 'fill #f) (hash-ref options 'stroke #f)) 2]
[(hash-ref options 'stroke #f) 1]
[else 0])])
(when (and mode (not (zero? mode)))
(send this addContent (format "~a Tr" mode))))
;; Character spacing
(when (and characterSpacing (not (zero? characterSpacing)))
(when (not (zero? characterSpacing))
(send this addContent (format "~a Tc" characterSpacing)))
;; Add the actual text
@ -162,10 +158,8 @@
;; used for embedded fonts.
(match-define (list encoded positions)
(cond
[(not (zero? wordSpacing))
(error 'unimplemented-brach)] ; todo
[else
(send (· this _font) encode text (hash-ref options 'features #f))]))
[(not (zero? wordSpacing)) (error 'unimplemented-brach)] ; todo
[else (send (· this _font) encode text (hash-ref options 'features #f))]))
(define scale (/ (· this _fontSize) 1000.0))
(define commands empty)
@ -174,10 +168,10 @@
;; Adds a segment of text to the TJ command buffer
(define (addSegment cur)
(when (< last cur)
(define hex (string-append* (sublist encoded last cur)))
(define advance (let ([pos (list-ref positions (sub1 cur))])
(- (· pos xAdvance) (· pos advanceWidth))))
(push-end! commands (format "<~a> ~a" hex (number (- advance)))))
(let* ([hex (string-append* (sublist encoded last cur))]
[posn (list-ref positions (sub1 cur))]
[advance (- (· posn xAdvance) (· posn advanceWidth))])
(push-end! commands (format "<~a> ~a" hex (number (- advance))))))
(set! last cur))
@ -190,18 +184,18 @@
(for/fold ([hadOffset #f] [x x])
([(pos i) (in-indexed positions)])
;; If we have an x or y offset, we have to break out of the current TJ command
;; so we can move the text position.
(define nextOffset
([(posn i) (in-indexed positions)])
(define havingOffset
(cond
[(or (not (zero? (· pos xOffset))) (not (zero? (· pos yOffset))))
;; If we have an x or y offset, we have to break out of the current TJ command
;; so we can move the text position.
[(or (not (zero? (· posn xOffset))) (not (zero? (· posn yOffset))))
;; Flush the current buffer
(flush i)
;; Move the text position and flush just the current character
(send this addContent (format "1 0 0 1 ~a ~a Tm"
(number (+ x (* (· pos xOffset) scale)))
(number (+ y (* (· pos yOffset) scale)))))
(number (+ x (* (· posn xOffset) scale)))
(number (+ y (* (· posn yOffset) scale)))))
(flush (add1 i))
#t]
[else
@ -210,12 +204,12 @@
(send this addContent (format "1 0 0 1 ~a ~a Tm" (number x) (number y))))
;; Group segments that don't have any advance adjustments
(unless (zero? (- (· pos xAdvance) (· pos advanceWidth)))
(unless (zero? (- (· posn xAdvance) (· posn advanceWidth)))
(addSegment (add1 i)))
#f]))
(values nextOffset (+ x (* (· pos xAdvance) scale))))
(values havingOffset (+ x (* (· posn xAdvance) scale))))
;; Flush any remaining commands

@ -174,11 +174,8 @@
(if (and (string? rule) (regexp-match #rx"^even-?odd$" rule)) "*" ""))
(define/contract (fill this color [rule #f])
((color-string?) ((or/c string? #f)) . ->*m . object?)
(when (regexp-match #rx"^(even-?odd)|(non-?zero)$" color)
(set! rule color)
(set! color #f))
(define/contract (fill this [color #f] #:rule [rule #f])
(() ((or/c color-string? #f) #:rule (or/c string? #f)) . ->*m . object?)
(when color (send this fillColor color)) ;; fillColor method is from color mixin
(send this addContent (format "f~a" (_windingRule rule))))
@ -191,8 +188,7 @@
(define/contract (fillAndStroke this [fill #f] [stroke fill] #:rule [rule #f])
(() ((or/c color-string? #f) (or/c color-string? #f) #:rule (or/c string? #f)) . ->*m . object?)
(when fill
(send* this [fillColor fill] [strokeColor stroke]))
(when fill (send* this [fillColor fill] [strokeColor stroke]))
(send this addContent (format "B~a" (_windingRule rule))))

Loading…
Cancel
Save