diff --git a/pitfall/pitfall/alltest.rkt b/pitfall/pitfall/alltest.rkt index 4885e8d4..bdc7ce41 100644 --- a/pitfall/pitfall/alltest.rkt +++ b/pitfall/pitfall/alltest.rkt @@ -1,7 +1,7 @@ #lang racket (module+ test - (require pitfall/test/test0 - pitfall/test/test1 - pitfall/test/test2 + (require #;pitfall/test/test0 + #;pitfall/test/test1 + #;pitfall/test/test2 pitfall/test/test3 - pitfall/page-test)) \ No newline at end of file + #;pitfall/page-test)) \ No newline at end of file diff --git a/pitfall/pitfall/document.rkt b/pitfall/pitfall/document.rkt index 5e4fa4c7..2f8966b3 100644 --- a/pitfall/pitfall/document.rkt +++ b/pitfall/pitfall/document.rkt @@ -1,8 +1,9 @@ #lang pitfall/racket -(require "reference.rkt" "object.rkt" "page.rkt" "vector.rkt" "color.rkt" "text.rkt") +(require "reference.rkt" "object.rkt" "page.rkt") +(require "mixins/vector.rkt" "mixins/color.rkt" "mixins/fonts.rkt" "mixins/text.rkt") (provide PDFDocument) -(define mixed% (text-mixin (color-mixin (vector-mixin object%)))) +(define mixed% (text-mixin (fonts-mixin (color-mixin (vector-mixin object%))))) (define PDFDocument (class mixed% ; actually is an instance of readable.Stream, which is an input port @@ -39,7 +40,7 @@ ;; Initialize mixins (· this initColor) (· this initVector) - #;(· this initFonts) ; todo + (· this initFonts) (· this initText) ; todo #;(· this initImages) ; todo diff --git a/pitfall/pitfall/font.rkt b/pitfall/pitfall/font.rkt new file mode 100644 index 00000000..e43033f4 --- /dev/null +++ b/pitfall/pitfall/font.rkt @@ -0,0 +1,2 @@ +#lang pitfall/racket + diff --git a/pitfall/pitfall/color.rkt b/pitfall/pitfall/mixins/color.rkt similarity index 100% rename from pitfall/pitfall/color.rkt rename to pitfall/pitfall/mixins/color.rkt diff --git a/pitfall/pitfall/mixins/fonts.rkt b/pitfall/pitfall/mixins/fonts.rkt new file mode 100644 index 00000000..11eedf79 --- /dev/null +++ b/pitfall/pitfall/mixins/fonts.rkt @@ -0,0 +1,80 @@ +#lang pitfall/racket +(require "../font.rkt") +(provide fonts-mixin) + +(define (fonts-mixin [% mixin-tester%]) + (class % + (super-new) + ;; Lookup table for embedded fonts + (field [_fontFamilies #f] + [_fontCount #f] + + ;; Font state + [_fontSize #f] + [_font #f] + [_registeredFonts #f]) + + (as-methods + initFonts + font))) + + +(define/contract (initFonts this) + (->m void?) + (set-field! _fontFamilies this (mhash)) + (set-field! _fontCount this 0) + + (set-field! _fontSize this 12) + (set-field! _font this null) + + (set-field! _registeredFonts this (mhash)) + + ;; set the default font + + (send this font "Helvetica") + (void)) + + +(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))) + ;; 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))]) + + (when size (set-field! fontSize this size)) + + ;; fast path: check if the font is already in the PDF + (cond + [(hash-ref (· this _fontFamilies) cacheKey #f) => + (λ (val) + (set-field! _font this val) + this)] + ;; load the font + [else + (define id (format "F~a" (increment-field! _fontCount this))) + (set-field! _font this (PDFFont-open this src family id)) + ;; check for existing font familes with the same name already in the PDF + ;; useful if the font was passed as a buffer + (let ([font (· this _fontFamilies (· this _font name))]) + (cond + [font (set-field! _font this font)] + ;; save the font for reuse later + [else (when cacheKey + (hash-set! (· this _fontFamilies) cacheKey (· this _font))) + (hash-set! (· this _fontFamilies) name (· this _font))])) + this])) + +(module+ test + (define fo (new (fonts-mixin)))) diff --git a/pitfall/pitfall/mixins/text.rkt b/pitfall/pitfall/mixins/text.rkt new file mode 100644 index 00000000..d7ba48ad --- /dev/null +++ b/pitfall/pitfall/mixins/text.rkt @@ -0,0 +1,175 @@ +#lang pitfall/racket +(provide text-mixin) + +(define (text-mixin [% mixin-tester%]) + (class % + (super-new) + (field [_lineGap #f] + [_textOptions #f]) + + (as-methods + initText + _initOptions + _text + _fragment + text + widthOfString))) + +(define/contract (initText this) + (->m void?) + (set-field! x this 0) + (set-field! y this 0) + (lineGap this 0) + (void)) + + +(define/contract (lineGap this _lineGap) + (number? . ->m . object?) + (set-field! _lineGap this _lineGap) + this) + + +(define/contract (moveDown this [lines 1] #:factor [factor 1]) + (() (number? #:factor number?) . ->*m . object?) + (increment-field! y this (* factor (send this currentLineHeight #t) (+ lines (· this _lineGap)))) + this) + + +(define/contract (moveUp this [lines 1]) + (() (number?) . ->*m . object?) + (moveDown this #:factor -1)) + + +(define/contract (_text this text x y options lineCallback) + (string? number? number? 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))]) + + this) + + +(define (text this text-string [x 0] [y 0] [options (mhash)]) + (send this _text text-string x y options (curry _line this))) + + +(define/contract (widthOfString this string [options (mhash)]) + ((string?) (hash?) . ->*m . number?) + 42 ; todo + ) + + +(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)) + + ;; 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)) + + ;; 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 + + options) + + +(define/contract (_line this text [options (mhash)] [wrapper #f]) + ((string?) (hash? (or/c procedure? #f)) . ->*m . void?) + (send this _fragment text (· this x) (· this y) options) + (define lineGap (or (hash-ref options 'lineGap #f) (· this _lineGap) 0)) + (if (not wrapper) + (increment-field! x this (send this widthOfString text)) + (increment-field! y (+ (send this currentLineHeight #t) lineGap))) + (void)) + + +(define/contract (_fragment this text x y options) + (string? number? number? hash? . ->m . void?) + + (define align (hash-ref options 'align 'left)) + (define wordSpacing (hash-ref options 'wordSpacing 0)) + (define characterSpacing (hash-ref options 'characterSpacing 0)) + + ;; text alignments ; todo + + ;; calculate the actual rendered width of the string after word and character spacing ; todo + + ;; create link annotations if the link option is given ; todo + + ;; create underline or strikethrough line ; todo + + ;; flip coordinate system + (send this save) + (send this transform 1 0 0 -1 0 (· this page height)) + (set! y (- (· this page height) y)) ; (@_font.ascender / 1000 * @_fontSize) ; todo + + ;; add current font to page if necessary + (hash-ref! (· this page fonts) (· this _font id) (λ () (· this font ref))) + + ;; begin the text object + (send this addContent "BT") + + ;; text position + (send this addContent (format "1 0 0 1 ~a ~a Tm" (number x) (number y))) + + ;; font and font size ; todo + + ;; 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))) + + ;; Character spacing + (when (and characterSpacing (not (zero? characterSpacing))) + (send this addContent (format "~a Tc" characterSpacing))) + + ;; Add the actual text + ;; If we have a word spacing value, we need to encode each word separately + ;; since the normal Tw operator only works on character code 32, which isn't + ;; used for embedded fonts. + ;; todo + + ;; Adds a segment of text to the TJ command buffer ; todo + + ;; Flushes the current TJ commands to the output stream ; todo + + ;; Flush any remaining commands ; todo + + ;; end the text object + (send this addContent "ET") + + ;; restore flipped coordinate system + (send this restore) + (display 'end-fragment)) + diff --git a/pitfall/pitfall/vector.rkt b/pitfall/pitfall/mixins/vector.rkt similarity index 99% rename from pitfall/pitfall/vector.rkt rename to pitfall/pitfall/mixins/vector.rkt index e2bfd541..5f3f65d4 100644 --- a/pitfall/pitfall/vector.rkt +++ b/pitfall/pitfall/mixins/vector.rkt @@ -1,5 +1,5 @@ #lang pitfall/racket -(require "path.rkt") +(require "../path.rkt") (provide vector-mixin default-ctm-value) (define (vector-mixin [% mixin-tester%]) diff --git a/pitfall/pitfall/test/test3rkt.pdf b/pitfall/pitfall/test/test3rkt.pdf index 645d84d6..e69de29b 100644 --- a/pitfall/pitfall/test/test3rkt.pdf +++ b/pitfall/pitfall/test/test3rkt.pdf @@ -1,69 +0,0 @@ -%PDF-1.3 -%ÿÿÿÿ -5 0 obj -<< -/Parent 1 0 R -/Resources 4 0 R -/Contents 3 0 R -/MediaBox [0 0 612 792] -/Type /Page ->> -endobj -4 0 obj -<< -/ProcSet [/PDF /Text /ImageB /ImageC /ImageI] ->> -endobj -3 0 obj -<< -/Length 63 ->> -stream -1 0 0 -1 0 792 cm -q -1 0 0 -1 0 792 cm -BT -1 0 0 1 0 792 Tm -ET -Q - -endstream -endobj -6 0 obj -<< -/CreationDate (D:19700101000000Z) -/Creator (PitfallKit) -/Producer (PitfallKit) ->> -endobj -2 0 obj -<< -/Pages 1 0 R -/Type /Catalog ->> -endobj -1 0 obj -<< -/Kids [5 0 R] -/Count 1 -/Type /Pages ->> -endobj -xref -0 7 -0000000000 65535 f -0000000448 00000 n -0000000399 00000 n -0000000186 00000 n -0000000119 00000 n -0000000015 00000 n -0000000299 00000 n -trailer -<< -/Info 6 0 R -/Root 2 0 R -/Size 7 ->> -startxref -505 -%%EOF diff --git a/pitfall/pitfall/text.rkt b/pitfall/pitfall/text.rkt index e80cbdd9..92ec1db6 100644 --- a/pitfall/pitfall/text.rkt +++ b/pitfall/pitfall/text.rkt @@ -104,7 +104,7 @@ (define/contract (_line this text [options (mhash)] [wrapper #f]) ((string?) (hash? (or/c procedure? #f)) . ->*m . void?) (send this _fragment text (· this x) (· this y) options) - (define lineGap (or (hash-ref options 'lineGap #f) (· this _lineGap) 0)) + (define lineGap (or (hash-ref options 'lineGap #f) (· this _lineGap) 0)) (if (not wrapper) (increment-field! x this (send this widthOfString text)) (increment-field! y (+ (send this currentLineHeight #t) lineGap))) @@ -129,9 +129,11 @@ ;; flip coordinate system (send this save) (send this transform 1 0 0 -1 0 (· this page height)) - (set! y (- (· this page height) y)) ; (@_font.ascender / 1000 * @_fontSize) ; todo + (set! y (- (· this page height) y (* (/ (· this _font ascender) 1000) (· this _fontSize)))) - ;; add current font to page if necessary ; todo + ;; add current font to page if necessary + (hash-ref! (· this page fonts) (· this _font id) (λ () (· this font ref))) + ;; begin the text object (send this addContent "BT")