diff --git a/pitfall/pitfall/document.rkt b/pitfall/pitfall/document.rkt index d5bba889..a38a369b 100644 --- a/pitfall/pitfall/document.rkt +++ b/pitfall/pitfall/document.rkt @@ -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 diff --git a/pitfall/pitfall/font.rkt b/pitfall/pitfall/font.rkt index 329fcf5d..5a5ac8eb 100644 --- a/pitfall/pitfall/font.rkt +++ b/pitfall/pitfall/font.rkt @@ -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))) \ No newline at end of file diff --git a/pitfall/pitfall/font/afm.rkt b/pitfall/pitfall/font/afm.rkt index a7a5afc3..036817f6 100644 --- a/pitfall/pitfall/font/afm.rkt +++ b/pitfall/pitfall/font/afm.rkt @@ -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{ diff --git a/pitfall/pitfall/font/standard-fonts.rkt b/pitfall/pitfall/font/standard-fonts.rkt index d367ea51..f8c6beb8 100644 --- a/pitfall/pitfall/font/standard-fonts.rkt +++ b/pitfall/pitfall/font/standard-fonts.rkt @@ -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 diff --git a/pitfall/pitfall/mixins/color.rkt b/pitfall/pitfall/mixins/color.rkt index dd729088..bd9c22fa 100644 --- a/pitfall/pitfall/mixins/color.rkt +++ b/pitfall/pitfall/mixins/color.rkt @@ -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)))) diff --git a/pitfall/pitfall/mixins/fonts.rkt b/pitfall/pitfall/mixins/fonts.rkt index 5c01e4d2..9f68fa3a 100644 --- a/pitfall/pitfall/mixins/fonts.rkt +++ b/pitfall/pitfall/mixins/fonts.rkt @@ -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)) diff --git a/pitfall/pitfall/mixins/text.rkt b/pitfall/pitfall/mixins/text.rkt index 7b15c7c6..3b1401a3 100644 --- a/pitfall/pitfall/mixins/text.rkt +++ b/pitfall/pitfall/mixins/text.rkt @@ -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 diff --git a/pitfall/pitfall/mixins/vector.rkt b/pitfall/pitfall/mixins/vector.rkt index 5f3f65d4..00bc3d6c 100644 --- a/pitfall/pitfall/mixins/vector.rkt +++ b/pitfall/pitfall/mixins/vector.rkt @@ -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))))