main
Matthew Butterick 5 years ago
parent c6b515d69e
commit 7287495bca

@ -4,6 +4,30 @@
;; structs
(struct $doc (options
pages
refs
root
info
opacity-registry
opacity-count
grad-count
current-fill-color
ctm
ctm-stack
font-families
font-count
current-font-size
current-font
registered-fonts
line-gap
text-options
x
y
image-registry
image-count) #:transparent #:mutable)
;; for JPEG and PNG
(struct $img (data label width height ref embed-proc) #:transparent #:mutable)

@ -15,103 +15,229 @@
"text.rkt"
"images.rkt"
"annotations.rkt")
(provide PDFDocument)
(define PDFDocument
(class (annotation-mixin
(image-mixin
(text-mixin
(fonts-mixin
(vector-mixin
(color-mixin (class object%
(super-new)
(field [@pages null])
(define/public (page) (first @pages))
(define/public (add-content data)
(page-write (first @pages) data)))))))))
(set-current-ref-id! 1)
(register-ref-listener (λ (ref) (store-ref ref)))
(super-new)
(init-field [(@options options) (mhasheq)])
(field [@refs null]
[@root (make-ref (mhasheq 'Type 'Catalog
'Pages (make-ref (mhasheq 'Type 'Pages))))]
;; initialize the metadata
[@info (mhasheq 'Producer "PITFALL"
'Creator "PITFALL"
'CreationDate (current-seconds))])
;; initialize mixins
(inherit-field @ctm) ; from vector mixin
(inherit-field @font-families) (inherit font) ; from font mixin
(inherit-field [@x x] [@y y]) ; from text
(inherit transform) ; from vector
(inherit-field @pages) (inherit page) ; from base
;; initialize params
(current-compress-streams? (hash-ref @options 'compress #t))
(current-auto-first-page (hash-ref @options 'autoFirstPage #t))
(when (current-auto-first-page) (add-page))
(when (current-auto-helvetica) (font "Helvetica"))
;; copy options
(for ([(key val) (in-hash (hash-ref @options 'info (hasheq)))])
(hash-set! @info key val))
(define/public (store-ref ref)
(set! @refs (cons ref @refs)))
(define/public (add-page [options-arg @options])
;; create a page object
(define page-parent (dict-ref @root 'Pages))
(set! @pages (cons (make-page page-parent options-arg) @pages))
(provide (all-defined-out))
(define (store-ref doc ref)
(set-$doc-refs! doc (cons ref ($doc-refs doc))))
(define (page doc) (first ($doc-pages doc)))
(define (add-content doc data)
(page-write (page doc) data))
(define (transform doc scaleX shearY shearX scaleY mdx mdy)
(define new-ctm (list scaleX shearY shearX scaleY mdx mdy))
(set-$doc-ctm! doc (combine-transforms ($doc-ctm doc) new-ctm))
(add-content doc (make-transform-string new-ctm)))
(define (make-$doc [options (make-hasheq)])
;; initial values
(define pages null)
(define refs null)
(define info (mhasheq 'Producer "PITFALL"
'Creator "PITFALL"
'CreationDate (current-seconds)))
(for ([(key val) (in-hash (hash-ref options 'info (hasheq)))])
(hash-set! info key val))
(define opacity-registry (make-hash))
(define opacity-count 0)
(define grad-count 0)
(define current-fill-color #false)
(define ctm default-ctm-value)
(define ctm-stack null)
(define font-families (make-hash))
(define font-count 0)
(define current-font-size 12)
(define current-font #false)
(define registered-fonts (make-hash))
(define line-gap 0)
(define text-options #false)
(define x 0)
(define y 0)
(define image-registry (make-hash))
(define image-count 0)
(define new-doc ($doc options
pages
refs
'dummy-root-value-that-will-be-replaced-below
info
opacity-registry
opacity-count
grad-count
current-fill-color
ctm
ctm-stack
font-families
font-count
current-font-size
current-font
registered-fonts
line-gap
text-options
x
y
image-registry
image-count))
(set-current-ref-id! 1)
(register-ref-listener (λ (ref) (store-ref new-doc ref)))
(set-$doc-root! new-doc (make-ref (mhasheq 'Type 'Catalog
'Pages (make-ref (mhasheq 'Type 'Pages)))))
;; initialize params
(current-compress-streams? (hash-ref options 'compress #t))
(current-auto-first-page (hash-ref options 'autoFirstPage #t))
(when (current-auto-first-page) (add-page new-doc))
#;(when (current-auto-helvetica) (font "Helvetica"))
new-doc)
(define (add-page doc [options-arg ($doc-options doc)])
;; create a page object
(define page-parent (dict-ref ($doc-root doc) 'Pages))
(set-$doc-pages! doc (cons (make-page page-parent options-arg) ($doc-pages doc)))
;; reset x and y coordinates
(set-$doc-x! doc (margin-left ($page-margins (page doc))))
(set-$doc-y! doc (margin-right ($page-margins (page doc))))
;; flip PDF coordinate system so that the origin is in
;; the top left rather than the bottom left
(set-$doc-ctm! doc default-ctm-value)
(transform doc 1 0 0 -1 0 ($page-height (page doc)))
doc)
(define (start-doc doc)
(write-bytes-out (format "%PDF-~a" (current-pdf-version)))
(write-bytes-out "%ÿÿÿÿ"))
(define (end-doc doc)
(for-each page-end ($doc-pages doc))
(define doc-info (make-ref))
(for ([(key val) (in-hash ($doc-info doc))])
(dict-set! doc-info key val))
(ref-end doc-info)
(for ([font (in-hash-values ($doc-font-families doc))])
(send font end))
(define pages-ref (dict-ref ($doc-root doc) 'Pages))
(dict-set! pages-ref 'Count (length ($doc-pages doc)))
(dict-set! pages-ref 'Kids (map $page-dictionary (reverse ($doc-pages doc))))
(ref-end pages-ref)
(ref-end ($doc-root doc))
;; reset x and y coordinates
(set! @x (margin-left ($page-margins (page))))
(set! @y (margin-right ($page-margins (page))))
;; flip PDF coordinate system so that the origin is in
;; the top left rather than the bottom left
(set! @ctm default-ctm-value)
(transform 1 0 0 -1 0 ($page-height (page)))
this)
(define/public (start-doc)
(write-bytes-out (format "%PDF-~a" (current-pdf-version)))
(write-bytes-out "%ÿÿÿÿ"))
(define/public (end-doc)
(for-each page-end @pages)
(define doc-info (make-ref))
(for ([(key val) (in-hash @info)])
(dict-set! doc-info key val))
(ref-end doc-info)
(define xref-offset (file-position (current-output-port)))
(write-bytes-out "xref")
(write-bytes-out (format "0 ~a" (add1 (length ($doc-refs doc)))))
(write-bytes-out "0000000000 65535 f ")
(for ([ref (in-list (reverse ($doc-refs doc)))])
(write-bytes-out
(string-append (~r ($ref-offset ref) #:min-width 10 #:pad-string "0") " 00000 n ")))
(write-bytes-out "trailer")
(write-bytes-out (convert (mhasheq 'Size (add1 (length ($doc-refs doc)))
'Root ($doc-root doc)
'Info doc-info)))
(write-bytes-out "startxref")
(write-bytes-out (numberizer xref-offset))
(write-bytes-out "%%EOF"))
#;(define PDFDocument
(class (annotation-mixin
(image-mixin
(text-mixin
(fonts-mixin
(vector-mixin
(color-mixin (class object%
(super-new)
(field [@pages null])
(define/public (page) (first @pages))
(define/public (add-content data)
(page-write (first @pages) data)))))))))
(set-current-ref-id! 1)
(register-ref-listener (λ (ref) (store-ref ref)))
(super-new)
(init-field [(@options options) (mhasheq)])
(field [@refs null]
[@root (make-ref (mhasheq 'Type 'Catalog
'Pages (make-ref (mhasheq 'Type 'Pages))))]
;; initialize the metadata
[@info (mhasheq 'Producer "PITFALL"
'Creator "PITFALL"
'CreationDate (current-seconds))])
;; initialize mixins
(inherit-field @ctm) ; from vector mixin
(inherit-field @font-families) (inherit font) ; from font mixin
(inherit-field [@x x] [@y y]) ; from text
(inherit transform) ; from vector
(inherit-field @pages) (inherit page) ; from base
;; initialize params
(current-compress-streams? (hash-ref @options 'compress #t))
(current-auto-first-page (hash-ref @options 'autoFirstPage #t))
(when (current-auto-first-page) (add-page))
(when (current-auto-helvetica) (font "Helvetica"))
;; copy options
(for ([(key val) (in-hash (hash-ref @options 'info (hasheq)))])
(hash-set! @info key val))
(define/public (store-ref ref)
(set! @refs (cons ref @refs)))
(define/public (add-page [options-arg @options])
;; create a page object
(define page-parent (dict-ref @root 'Pages))
(set! @pages (cons (make-page page-parent options-arg) @pages))
;; reset x and y coordinates
(set! @x (margin-left ($page-margins (page))))
(set! @y (margin-right ($page-margins (page))))
;; flip PDF coordinate system so that the origin is in
;; the top left rather than the bottom left
(set! @ctm default-ctm-value)
(transform 1 0 0 -1 0 ($page-height (page)))
this)
(define/public (start-doc)
(write-bytes-out (format "%PDF-~a" (current-pdf-version)))
(write-bytes-out "%ÿÿÿÿ"))
(define/public (end-doc)
(for-each page-end @pages)
(define doc-info (make-ref))
(for ([(key val) (in-hash @info)])
(dict-set! doc-info key val))
(ref-end doc-info)
(for ([font (in-hash-values @font-families)])
(send font end))
(for ([font (in-hash-values @font-families)])
(send font end))
(define pages-ref (dict-ref @root 'Pages))
(dict-set! pages-ref 'Count (length @pages))
(dict-set! pages-ref 'Kids (map $page-dictionary (reverse @pages)))
(ref-end pages-ref)
(define pages-ref (dict-ref @root 'Pages))
(dict-set! pages-ref 'Count (length @pages))
(dict-set! pages-ref 'Kids (map $page-dictionary (reverse @pages)))
(ref-end pages-ref)
(ref-end @root)
(ref-end @root)
(define xref-offset (file-position (current-output-port)))
(write-bytes-out "xref")
(write-bytes-out (format "0 ~a" (add1 (length @refs))))
(write-bytes-out "0000000000 65535 f ")
(for ([ref (in-list (reverse @refs))])
(write-bytes-out
(string-append (~r ($ref-offset ref) #:min-width 10 #:pad-string "0") " 00000 n ")))
(write-bytes-out "trailer")
(write-bytes-out (convert (mhasheq 'Size (add1 (length @refs))
'Root @root
'Info doc-info)))
(write-bytes-out "startxref")
(write-bytes-out (numberizer xref-offset))
(write-bytes-out "%%EOF"))))
(define xref-offset (file-position (current-output-port)))
(write-bytes-out "xref")
(write-bytes-out (format "0 ~a" (add1 (length @refs))))
(write-bytes-out "0000000000 65535 f ")
(for ([ref (in-list (reverse @refs))])
(write-bytes-out
(string-append (~r ($ref-offset ref) #:min-width 10 #:pad-string "0") " 00000 n ")))
(write-bytes-out "trailer")
(write-bytes-out (convert (mhasheq 'Size (add1 (length @refs))
'Root @root
'Info doc-info)))
(write-bytes-out "startxref")
(write-bytes-out (numberizer xref-offset))
(write-bytes-out "%%EOF"))))
(module+ test
(define d (new PDFDocument)))
(define d (make-$doc)))

@ -12,56 +12,56 @@
(define (make-page [page-parent #false] [options (mhash)])
[define size (hash-ref options 'size "letter")]
[define layout (hash-ref options 'layout "portrait")]
[define dimensions (if (list? size)
size
(hash-ref page-sizes (string-upcase size)))]
[define width (list-ref dimensions (if (equal? layout "portrait") 0 1))]
[define height (list-ref dimensions (if (equal? layout "portrait") 1 0))]
[define content (make-ref)]
[define resources (make-ref (mhash 'ProcSet '(PDF Text ImageB ImageC ImageI)))]
[define margins
(let ([margin-value (hash-ref options 'margin #f)])
(if (number? margin-value)
(margin margin-value margin-value margin-value margin-value)
(hash-ref options 'margins (current-default-margins))))]
;; The page dictionary
[define dictionary
(make-ref
(mhash 'Type 'Page
'Parent page-parent
'MediaBox (list 0 0 width height)
'Contents content
'Resources resources))]
[define layout (hash-ref options 'layout "portrait")]
[define dimensions (if (list? size)
size
(hash-ref page-sizes (string-upcase size)))]
[define width (list-ref dimensions (if (equal? layout "portrait") 0 1))]
[define height (list-ref dimensions (if (equal? layout "portrait") 1 0))]
[define content (make-ref)]
[define resources (make-ref (mhash 'ProcSet '(PDF Text ImageB ImageC ImageI)))]
[define margins
(let ([margin-value (hash-ref options 'margin #f)])
(if (number? margin-value)
(margin margin-value margin-value margin-value margin-value)
(hash-ref options 'margins (current-default-margins))))]
;; The page dictionary
[define dictionary
(make-ref
(mhash 'Type 'Page
'Parent page-parent
'MediaBox (list 0 0 width height)
'Contents content
'Resources resources))]
($page page-parent options size layout dimensions width height content resources margins dictionary))
(define (page-fonts p)
(dict-ref! ($page-resources p) 'Font (make-hasheq)))
(define (page-fonts p)
(dict-ref! ($page-resources p) 'Font (make-hasheq)))
(define (page-xobjects p)
(dict-ref! ($page-resources p) 'XObject (make-hasheq)))
(define (page-xobjects p)
(dict-ref! ($page-resources p) 'XObject (make-hasheq)))
(define (page-ext_gstates p)
(dict-ref! ($page-resources p) 'ExtGState (make-hasheq)))
(define (page-ext_gstates p)
(dict-ref! ($page-resources p) 'ExtGState (make-hasheq)))
(define (page-patterns p)
(dict-ref! ($page-resources p) 'Pattern (make-hasheq)))
(define (page-patterns p)
(dict-ref! ($page-resources p) 'Pattern (make-hasheq)))
(define (page-annotations p [annot #f])
(if annot
(dict-update! ($page-dictionary p) 'Annots (λ (val) (cons annot val)) null)
(dict-ref! ($page-dictionary p) 'Annots null)))
(define (page-annotations p [annot #f])
(if annot
(dict-update! ($page-dictionary p) 'Annots (λ (val) (cons annot val)) null)
(dict-ref! ($page-dictionary p) 'Annots null)))
(define (page-maxY p)
(- ($page-height p) (margin-bottom ($page-margins p))))
(define (page-maxY p)
(- ($page-height p) (margin-bottom ($page-margins p))))
(define (page-write p chunk)
(ref-write ($page-content p) chunk))
(define (page-write p chunk)
(ref-write ($page-content p) chunk))
(define (page-end p)
(ref-end ($page-dictionary p))
(ref-end ($page-resources p))
(ref-end ($page-content p)))
(define (page-end p)
(ref-end ($page-dictionary p))
(ref-end ($page-resources p))
(ref-end ($page-content p)))
(define page-sizes

@ -37,10 +37,10 @@
(time
(with-output-to-file ps
(λ ()
(define doc (make-object PDFDocument (hash 'compress compress?)))
(send doc start-doc)
(define doc (make-$doc (hash 'compress compress?)))
(start-doc doc)
(proc doc)
(send doc end-doc))
(end-doc doc))
#:exists 'replace))
(when test?
(check-headers-equal? ps (this->control ps))

@ -1,6 +1,6 @@
#lang racket/base
(require
"helper.rkt"
"core.rkt"
racket/class
racket/match
racket/string
@ -9,10 +9,12 @@
sugar/unstable/js
sugar/unstable/dict
"path.rkt")
(provide vector-mixin default-ctm-value)
(provide vector-mixin default-ctm-value combine-transforms make-transform-string)
(define default-ctm-value '(1 0 0 1 0 0))
(define (vector-mixin [% mixin-tester%])
(class %
(super-new)
@ -39,16 +41,16 @@
(define/public (line-cap [c #f])
(define cap-styles (hasheq 'butt 0 'round 1 'square 2))
(add-content
(format "~a J" (if (symbol? c)
(hash-ref cap-styles c)
""))))
(format "~a J" (if (symbol? c)
(hash-ref cap-styles c)
""))))
(define/public (line-join [j #f])
(define cap-styles (hasheq 'miter 0 'round 1 'bevel 2))
(add-content
(format "~a j" (if (symbol? j)
(hash-ref cap-styles j)
""))))
(format "~a j" (if (symbol? j)
(hash-ref cap-styles j)
""))))
(define/public (line-width w)
(add-content (format "~a w" (number w))))
@ -57,9 +59,9 @@
(cond
[(list? length)
(add-content
(format "[~a] ~a d"
(string-join (map number length) " ")
(hash-ref options 'phase 0)))]
(format "[~a] ~a d"
(string-join (map number length) " ")
(hash-ref options 'phase 0)))]
[length
(define space (hash-ref options 'space length))
(define phase (hash-ref options 'phase 0))
@ -172,6 +174,9 @@
(+ (* m0 dx) (* m2 dy) m4)
(+ (* m1 dx) (* m3 dy) m5)))
(define (make-transform-string ctm)
(format "~a cm" (string-join (map numberizer ctm) " ")))
(module+ test
(require rackunit)
(define ctm default-ctm-value)

Loading…
Cancel
Save