From b0f96554b85620265d4f6ec9f20512cf566289a0 Mon Sep 17 00:00:00 2001 From: Matthew Butterick Date: Fri, 21 Dec 2018 12:33:40 -0800 Subject: [PATCH] gingerly --- pitfall/pitfall/annotations.rkt | 2 +- pitfall/pitfall/core.rkt | 28 ++++ pitfall/pitfall/document.rkt | 262 +++++++++++++++---------------- pitfall/pitfall/embedded.rkt | 3 +- pitfall/pitfall/fonts.rkt | 8 +- pitfall/pitfall/jpeg-structy.rkt | 2 +- pitfall/pitfall/object.rkt | 2 +- pitfall/pitfall/old/parse.rkt | 2 +- pitfall/pitfall/param.rkt | 8 - pitfall/pitfall/pdftest.rkt | 9 +- pitfall/pitfall/reference.rkt | 2 +- pitfall/pitfall/struct.rkt | 9 -- 12 files changed, 171 insertions(+), 166 deletions(-) create mode 100644 pitfall/pitfall/core.rkt delete mode 100644 pitfall/pitfall/param.rkt delete mode 100644 pitfall/pitfall/struct.rkt diff --git a/pitfall/pitfall/annotations.rkt b/pitfall/pitfall/annotations.rkt index c79c9886..06406bf6 100644 --- a/pitfall/pitfall/annotations.rkt +++ b/pitfall/pitfall/annotations.rkt @@ -1,6 +1,6 @@ #lang racket/base (require - "struct.rkt" + "core.rkt" racket/class racket/match racket/contract diff --git a/pitfall/pitfall/core.rkt b/pitfall/pitfall/core.rkt new file mode 100644 index 00000000..47e0a9e9 --- /dev/null +++ b/pitfall/pitfall/core.rkt @@ -0,0 +1,28 @@ +#lang racket/base +(provide (all-defined-out)) + +;; structs + +(struct String (string) #:transparent) + +;; for JPEG and PNG +(struct image (label width height obj) #:transparent #:mutable) + +;; params + +(define test-mode (make-parameter #f)) +(define current-compress-streams? (make-parameter #f)) + +(define current-pdf-version (make-parameter 1.3)) +(define current-auto-first-page (make-parameter #t)) +(define current-doc-offset (make-parameter 'doc-offset-not-initialized)) + +;; helpers + +(define (numberizer x #:round [round? #true]) + (unless (and (number? x) (< -1e21 x 1e21)) + (raise-argument-error 'number "valid number" x)) + (let ([x (if round? (/ (round (* x 1e6)) 1e6) x)]) + (number->string (if (integer? x) + (inexact->exact x) + x)))) \ No newline at end of file diff --git a/pitfall/pitfall/document.rkt b/pitfall/pitfall/document.rkt index 4b73c038..4c04d3da 100644 --- a/pitfall/pitfall/document.rkt +++ b/pitfall/pitfall/document.rkt @@ -1,14 +1,11 @@ #lang debug racket/base (require - "param.rkt" - "struct.rkt" + "core.rkt" racket/class racket/format racket/generator racket/match racket/list - sugar/unstable/class - sugar/unstable/js sugar/unstable/dict "reference.rkt" "object.rkt" @@ -21,136 +18,135 @@ "annotations.rkt") (provide PDFDocument) -(define mixed% (annotation-mixin (image-mixin (text-mixin (fonts-mixin (color-mixin (vector-mixin object%))))))) - -(define-subclass mixed% (PDFDocument [options (mhash)]) - (field [@pageBuffer null] - [@offsets (mhasheqv)] ; The PDF object store - - [ref-gen (generator () - (let loop ([refid 1]) - (hash-set! @offsets refid 'missing-ref-offset) - (yield refid) - (loop (add1 refid))))] - [(@root _root) (ref (mhasheq 'Type "Catalog" - 'Pages (ref (mhasheq 'Type "Pages" - 'Count 0 - 'Kids empty))))] ; top object - [(@page page) #f] ; The current page - [(@x x) 0] - [(@y y) 0] - [(@info info) (mhasheq - 'Producer "PITKIT" - 'Creator "PITKIT" - 'CreationDate (seconds->date (if (test-mode) - 0 - (current-seconds)) #f))]) ; Initialize the metadata - - ;; Initialize mixins - (· this initColor) - (· this initVector) - (· this initFonts) - (inherit-field _fontFamilies) - (· this initText) - (· this initImages) - - ;; initialize params - (current-compress-streams? (hash-ref options 'compress #t)) - (current-auto-first-page (hash-ref options 'autoFirstPage #t)) - (current-doc-offset 0) - - (define/public (ref [payload (mhash)]) - (make-object PDFReference this (ref-gen) payload)) - - (define/public (write x) - (define bstr (if (bytes? x) x (string->bytes/latin-1 (string-append x "\n")))) - (write-bytes bstr) - (current-doc-offset (file-position (current-output-port)))) +(define PDFDocument + (class (annotation-mixin (image-mixin (text-mixin (fonts-mixin (color-mixin (vector-mixin object%)))))) + (super-new) + (init-field [(@options options) (mhasheq)]) + (field [@pages null] + [@offsets (mhasheqv)] ; The PDF object stores + [ref-gen (generator () + (let loop ([refid 1]) + (hash-set! @offsets refid #f) + (yield refid) + (loop (add1 refid))))] + [(@root _root) (ref (mhasheq 'Type "Catalog" + 'Pages (ref (mhasheq 'Type "Pages" + 'Count 0 + 'Kids empty))))] ; top object + [(@x x) 0] + [(@y y) 0] + [(@info info) (mhasheq + 'Producer "PITFALL" + 'Creator "PITFALL" + 'CreationDate (seconds->date (if (test-mode) + 0 + (current-seconds)) #f))]) ; Initialize the metadata + + ;; Initialize mixins + (send this initColor) + (send this initVector) + (inherit-field _ctm) + (send this initFonts) + (inherit-field @font-families) + (send this initText) + (send this initImages) + + ;; initialize params + (current-compress-streams? (hash-ref @options 'compress #t)) + (current-auto-first-page (hash-ref @options 'autoFirstPage #t)) + (current-doc-offset 0) + + (define/public (page) (first @pages)) + + (define/public (ref [payload (mhasheq)]) + (make-object PDFReference this (ref-gen) payload)) + + (define/public (write x) + (define bstr (if (bytes? x) x (string->bytes/latin-1 (string-append x "\n")))) + (write-bytes bstr) + (current-doc-offset (file-position (current-output-port)))) - (define/public (addPage [options-arg options]) - ;; end the current page if needed - (unless (hash-ref options 'bufferPages #f) - (flushPages)) - - ;; create a page object - (set! @page (make-object PDFPage this options-arg)) - (set! @pageBuffer (cons @page @pageBuffer)) + (define/public (addPage [options-arg @options]) + ;; end the current page if needed + (unless (hash-ref @options 'bufferPages #f) + (flush-pages)) + + ;; create a page object + (set! @pages (cons (make-object PDFPage this options-arg) @pages)) - ;; in Kids, store page dictionaries in correct order - ;; this determines order in document - (define pages (· @root payload Pages payload)) - (hash-update! pages 'Kids (λ (val) (append val (list (· @page dictionary))))) - (hash-set! pages 'Count (length (hash-ref pages 'Kids))) - - ;; reset x and y coordinates - (set! @x (· @page margins left)) - (set! @y (· @page margins top)) - ;; flip PDF coordinate system so that the origin is in - ;; the top left rather than the bottom left - (set-field! _ctm this default-ctm-value) - (send this transform 1 0 0 -1 0 (· @page height)) - this) - - (define/public (flushPages) - (for-each (λ (p) (· p end)) @pageBuffer) - (set! @pageBuffer empty)) - - (define/public (addContent data) - (send @page write data) - this) - - (define/public (_refEnd aref) - (hash-set! @offsets (· aref id) (· aref offset))) - - (define/public (end) ; called from source file to finish doc - ;; Write the header - (write (format "%PDF-~a" (current-pdf-version))) ; PDF version - (write (string-append "%" (list->string (map integer->char (make-list 4 #xFF))))) ; 4 binary chars, as recommended by the spec - - (flushPages) - (define _info (ref)) - (for ([(key val) (in-hash @info)]) - ;; upgrade string literal to String struct - (hash-set! (· _info payload) key (if (string? val) (String val) val))) - (· _info end) + ;; in Kids, store page dictionaries in correct order + ;; this determines order in document + (define pages (get-field payload (hash-ref (get-field payload @root) 'Pages))) + (hash-update! pages 'Kids (λ (val) (append val (list (get-field dictionary (page)))))) + (hash-set! pages 'Count (length (hash-ref pages 'Kids))) + + ;; reset x and y coordinates + (set! @x (hash-ref (get-field margins (page)) 'left)) + (set! @y (hash-ref (get-field margins (page)) 'top)) + ;; flip PDF coordinate system so that the origin is in + ;; the top left rather than the bottom left + (set! _ctm default-ctm-value) + (send this transform 1 0 0 -1 0 (get-field height (page))) + this) + + (define/public (flush-pages) + (for-each (λ (p) (send p end)) @pages) + (set! @pages empty)) + + (define/public (addContent data) + (send (page) write data) + this) + + (define/public (_refEnd aref) + (hash-set! @offsets (get-field id aref) (get-field offset aref))) + + (define/public (end) ; called from source file to finish doc + (write (format "%PDF-~a" (current-pdf-version))) + (write (string-append "%" (list->string (map integer->char (make-list 4 #xFF))))) + (flush-pages) + (define doc-info (ref)) + (for ([(key val) (in-hash @info)]) + ;; upgrade string literal to String struct + (hash-set! (get-field payload doc-info) key (if (string? val) (String val) val))) + (send doc-info end) - (for ([font (in-hash-values _fontFamilies)]) - (· font finalize)) - (· @root end) - (· @root payload Pages end) - - (define xref-offset (current-doc-offset)) - (match-define (list this-idxs this-offsets) - (match (sort (hash->list @offsets) < #:key car) ; sort by refid - [(list (cons idxs offsets) ...) (list idxs offsets)])) - (write "xref") - (write (format "0 ~a" (add1 (length this-offsets)))) - (write "0000000000 65535 f ") - (let ([missing-offsets (for/list ([offset (in-list this-offsets)] - [idx (in-list this-idxs)] - #:unless (number? offset)) - idx)]) - (unless (empty? missing-offsets) - (raise-argument-error 'document:end "numerical offsets" missing-offsets))) - (for ([offset (in-list this-offsets)] - [idx (in-list this-idxs)]) - (write (string-append (~r offset #:min-width 10 #:pad-string "0") " 00000 n "))) + (for ([font (in-hash-values @font-families)]) + (send font finalize)) + (send @root end) + (send (hash-ref (get-field payload @root) 'Pages) end) + + (define xref-offset (current-doc-offset)) + (match-define (list this-idxs this-offsets) + (match (sort (hash->list @offsets) < #:key car) ; sort by refid + [(list (cons idxs offsets) ...) (list idxs offsets)])) + (write "xref") + (write (format "0 ~a" (add1 (length this-offsets)))) + (write "0000000000 65535 f ") + (let ([missing-offsets (for/list ([offset (in-list this-offsets)] + [idx (in-list this-idxs)] + #:unless (number? offset)) + idx)]) + (unless (empty? missing-offsets) + (raise-argument-error 'document:end "numerical offsets" missing-offsets))) + (for ([offset (in-list this-offsets)] + [idx (in-list this-idxs)]) + (write (string-append (~r offset #:min-width 10 #:pad-string "0") " 00000 n "))) - (write "trailer") - (write (convert - (mhash 'Size (add1 (length this-offsets)) - 'Root @root - 'Info _info))) - (write "startxref") - (write (number xref-offset)) - (write "%%EOF")) - - ; if no 'info key, nothing will be copied from (hash) - (for ([(key val) (in-hash (hash-ref options 'info (hash)))]) - (hash-set! @info key val)) - - ;; Add the first page - (when (current-auto-first-page) (addPage))) - -(module+ test - (define d (new PDFDocument))) \ No newline at end of file + (write "trailer") + (write (convert + (mhash 'Size (add1 (length this-offsets)) + 'Root @root + 'Info doc-info))) + (write "startxref") + (write (numberizer xref-offset)) + (write "%%EOF")) + + ; if no 'info key, nothing will be copied from (hash) + (for ([(key val) (in-hash (hash-ref @options 'info (hash)))]) + (hash-set! @info key val)) + + ;; Add the first page + (when (current-auto-first-page) (addPage)))) + + (module+ test + (define d (new PDFDocument))) \ No newline at end of file diff --git a/pitfall/pitfall/embedded.rkt b/pitfall/pitfall/embedded.rkt index 84ad281a..218bedb5 100644 --- a/pitfall/pitfall/embedded.rkt +++ b/pitfall/pitfall/embedded.rkt @@ -1,8 +1,7 @@ #lang debug racket/base (require (for-syntax racket/base) - "param.rkt" - "struct.rkt" + "core.rkt" racket/class racket/match racket/string diff --git a/pitfall/pitfall/fonts.rkt b/pitfall/pitfall/fonts.rkt index 37cc331d..aaf4fa6a 100644 --- a/pitfall/pitfall/fonts.rkt +++ b/pitfall/pitfall/fonts.rkt @@ -13,7 +13,7 @@ (class % (super-new) ;; Lookup table for embedded fonts - (field [_fontFamilies #f] + (field [@font-families #f] [_fontCount #f] ;; Font state @@ -31,7 +31,7 @@ (define/contract (initFonts this) (->m void?) - (set-field! _fontFamilies this (mhash)) + (set-field! @font-families this (mhash)) (set-field! _fontCount this 0) (set-field! _fontSize this 12) @@ -66,7 +66,7 @@ ;; fast path: check if the font is already in the PDF (cond - [(hash-ref (· this _fontFamilies) cacheKey #f) => + [(hash-ref (· this @font-families) cacheKey #f) => (λ (val) (set-field! _font this val))] ;; load the font @@ -77,7 +77,7 @@ ;; check for existing font familes with the same name already in the PDF ;; useful if the font was passed as a buffer - (let* ([this-ff (· this _fontFamilies)] + (let* ([this-ff (· this @font-families)] [this-f (· this _font)] [font (hash-ref this-ff (· this-f name) #f)]) (cond diff --git a/pitfall/pitfall/jpeg-structy.rkt b/pitfall/pitfall/jpeg-structy.rkt index 2821d20c..399b1cbb 100644 --- a/pitfall/pitfall/jpeg-structy.rkt +++ b/pitfall/pitfall/jpeg-structy.rkt @@ -1,6 +1,6 @@ #lang debug racket/base (require - "struct.rkt" + "core.rkt" racket/class racket/contract sugar/unstable/class diff --git a/pitfall/pitfall/object.rkt b/pitfall/pitfall/object.rkt index d9dfdfbb..2cb3cea1 100644 --- a/pitfall/pitfall/object.rkt +++ b/pitfall/pitfall/object.rkt @@ -1,6 +1,6 @@ #lang racket/base (require - "struct.rkt" + "core.rkt" racket/class racket/string racket/contract diff --git a/pitfall/pitfall/old/parse.rkt b/pitfall/pitfall/old/parse.rkt index 90e19d4f..7e210d2e 100644 --- a/pitfall/pitfall/old/parse.rkt +++ b/pitfall/pitfall/old/parse.rkt @@ -1,5 +1,5 @@ #lang at-exp br/quicklang -(require "parser.rkt" "tokenizer.rkt" "struct.rkt" gregor racket/bytes) +(require "parser.rkt" "tokenizer.rkt" "core.rkt" gregor racket/bytes) (provide (matching-identifiers-out #rx"pf-" (all-defined-out))) (module+ test (require rackunit)) diff --git a/pitfall/pitfall/param.rkt b/pitfall/pitfall/param.rkt deleted file mode 100644 index 5333f36a..00000000 --- a/pitfall/pitfall/param.rkt +++ /dev/null @@ -1,8 +0,0 @@ -#lang racket/base -(provide (all-defined-out)) -(define test-mode (make-parameter #f)) -(define current-compress-streams? (make-parameter #f)) - -(define current-pdf-version (make-parameter 1.3)) -(define current-auto-first-page (make-parameter #t)) -(define current-doc-offset (make-parameter 'doc-offset-not-initialized)) \ No newline at end of file diff --git a/pitfall/pitfall/pdftest.rkt b/pitfall/pitfall/pdftest.rkt index ad19c189..9d1bbb4c 100644 --- a/pitfall/pitfall/pdftest.rkt +++ b/pitfall/pitfall/pdftest.rkt @@ -1,7 +1,7 @@ #lang racket/base (require (for-syntax racket/base) - "param.rkt" + "core.rkt" racket/class racket/string br/define @@ -35,13 +35,12 @@ (define (make-doc ps [compress? #false] [proc (λ (doc) doc)] #:test [test? #t] #:pdfkit [pdfkit? #t]) (time - (let () - (define f (open-output-file ps #:exists 'replace)) - (parameterize ([current-output-port f]) + (with-output-to-file ps + (λ () (define doc (make-object PDFDocument (hash 'compress compress?))) (proc doc) (send doc end)) - (close-output-port f))) + #:exists 'replace)) (when test? (check-pdfs-equal? ps (this->control ps)) (when pdfkit? diff --git a/pitfall/pitfall/reference.rkt b/pitfall/pitfall/reference.rkt index b69d28fc..9fccec52 100644 --- a/pitfall/pitfall/reference.rkt +++ b/pitfall/pitfall/reference.rkt @@ -2,7 +2,7 @@ (require racket/class racket/match racket/port - "param.rkt" + "core.rkt" "object.rkt" "zlib.rkt") (provide PDFReference) diff --git a/pitfall/pitfall/struct.rkt b/pitfall/pitfall/struct.rkt deleted file mode 100644 index 1112a388..00000000 --- a/pitfall/pitfall/struct.rkt +++ /dev/null @@ -1,9 +0,0 @@ -#lang racket/base -(provide (all-defined-out)) - -;; use structs to sub for missing node types - -(struct String (string) #:transparent) - -;; for JPEG and PNG -(struct image (label width height obj) #:transparent #:mutable) \ No newline at end of file