fix port as PostScript/PDF/SVG destination

by putting port-calling work in a specific thread
This commit is contained in:
Matthew Flatt 2011-01-18 19:44:04 -07:00
parent 4fc8797df2
commit 784c3de563
5 changed files with 89 additions and 45 deletions

View File

@ -40,7 +40,7 @@
(output-port? output))
(raise-type-error (get-name) "path string, output port, or #f" output)))
(define-values (s port-box close-port? width height landscape?)
(define-values (s port close-port? writer width height landscape?)
(let ([su (if interactive
((gui-dynamic-require 'get-ps-setup-from-user) #f parent)
(current-ps-setup))])
@ -68,7 +68,7 @@
#f)])
(if (and to-file?
(not fn))
(values #f #f #f #f #f #f)
(values #f #f #f #f #f #f #f)
(let* ([paper (assoc (send pss get-paper-name) paper-sizes)]
[w (if (or (not init-w) use-paper-bbox)
(cadr paper)
@ -83,26 +83,26 @@
(or fn (make-temporary-file (if pdf?
"draw~a.pdf"
"draw~a.ps")))
#:exists 'truncate/replace))]
[port-box (make-immobile file)])
#:exists 'truncate/replace))])
(let-values ([(w h) (if (and pdf? landscape?)
(values h w)
(values w h))])
(values w h))]
[(writer proc) (make-port-writer file)])
(values
((if pdf?
cairo_pdf_surface_create_for_stream
cairo_ps_surface_create_for_stream)
write_port_bytes
port-box
proc
w
h)
port-box ; needs to be accessible as long as `s'
file
(not (output-port? fn))
writer
w
h
landscape?)))))]
[else
(values #f #f #f #f #f #f)])))
(values #f #f #f #f #f #f #f)])))
(define-values (margin-x margin-y)
(if as-eps
@ -147,9 +147,11 @@
(cairo_destroy c)
(set! c #f)
(set! s #f)
(port-writer-wait writer)
(set! writer #f)
(when close-port?
(close-output-port (ptr-ref port-box _racket)))
(set! port-box #f))
(close-output-port port))
(set! port #f))
(define/override (init-cr-matrix c)
(cairo_translate c trans-x trans-y)

View File

@ -40,16 +40,17 @@
(define height init-h)
(define close-port? (path-string? init-output))
(define port-box ; needs to be accessible as long as `s' or `c'
(let ([output (if (output-port? init-output)
init-output
(open-output-file init-output #:exists exists))])
(make-immobile output)))
(define s (cairo_svg_surface_create_for_stream
write_port_bytes
port-box
width
height))
(define port
(if (output-port? init-output)
init-output
(open-output-file init-output #:exists exists)))
(define-values (s writer)
(let-values ([(writer proc) (make-port-writer port)])
(values (cairo_svg_surface_create_for_stream
proc
width
height)
writer)))
(define c (and s (cairo_create s)))
(when s (cairo_surface_destroy s))
@ -66,9 +67,11 @@
(cairo_destroy c)
(set! c #f)
(set! s #f)
(port-writer-wait writer)
(set! writer #f)
(when close-port?
(close-output-port (ptr-ref port-box _racket)))
(set! port-box #f))
(close-output-port port))
(set! port #f))
(define/override (get-pango font)
(send font get-pango))

View File

@ -4,14 +4,25 @@
"../unsafe/cairo.ss"
"../unsafe/bstr.ss")
(provide write_port_bytes
make-immobile)
(provide make-port-writer
port-writer-wait)
(define (write-port-bytes port-box bytes len)
(write-bytes (scheme_make_sized_byte_string bytes len 0)
(ptr-ref port-box _racket))
CAIRO_STATUS_SUCCESS)
(define (make-port-writer port)
(let ([t (thread/suspend-to-kill
(lambda ()
(let loop ()
(let ([msg (thread-receive)])
(when (bytes? msg)
(write-bytes msg port)
(loop))))))])
(values t
(lambda (bytes len)
(thread-send t (scheme_make_sized_byte_string bytes len 1)
void)
CAIRO_STATUS_SUCCESS))))
(define write_port_bytes (function-ptr write-port-bytes _cairo_write_func_t))
(define (port-writer-wait t)
(thread-resume t)
(thread-send t eof void)
(thread-wait t))
(define make-immobile ((allocator free-immobile-cell) malloc-immobile-cell))

View File

@ -237,21 +237,44 @@
#:wrap (allocator cairo_surface_destroy))
(define-cairo cairo_ps_surface_create (_fun _path _double* _double* -> _cairo_surface_t)
#:wrap (allocator cairo_surface_destroy))
(define-cairo cairo_ps_surface_create_for_stream
;; The _fpointer argument is _cairo_write_func_t
;; but it's saved as a callback, so care is needed with
;; allocation.
(_fun _fpointer _pointer _double* _double* -> _cairo_surface_t)
#:wrap (allocator cairo_surface_destroy))
;; Stream surfaces
;; The first argument to a stream-surface creation
;; function is a procedure, and we need the procedure to
;; live just as long as the surface. Implement that by
;; saving the closure via user data on the surface.
;; Externally, a stream-creation function takes
;; just a closure --- not a function and data.
(define _cairo_write_func_t
(_fun #:atomic? #t _pointer _pointer _uint -> _int))
(define _stream-surface-proc
(_fun _cairo_write_func_t _pointer _double* _double* -> _cairo_surface_t))
(define cell-key (malloc 1 'raw))
(define stream-surface-allocator
(lambda (p)
((allocator cairo_surface_destroy)
(lambda (proc w h)
(let* ([new-proc (lambda (null bytes len)
(proc bytes len))]
[s (p new-proc #f w h)]
[b (malloc-immobile-cell new-proc)])
(cairo_surface_set_user_data s cell-key b free-immobile-cell)
s)))))
(define-cairo cairo_ps_surface_create_for_stream
_stream-surface-proc
#:wrap stream-surface-allocator)
(define-cairo cairo_pdf_surface_create_for_stream
;; As above:
(_fun _fpointer _pointer _double* _double* -> _cairo_surface_t)
#:wrap (allocator cairo_surface_destroy))
_stream-surface-proc
#:wrap stream-surface-allocator)
(define-cairo cairo_svg_surface_create_for_stream
;; As above:
(_fun _fpointer _pointer _double* _double* -> _cairo_surface_t)
#:wrap (allocator cairo_surface_destroy))
(define/provide _cairo_write_func_t (_fun _pointer _pointer _uint -> _int))
_stream-surface-proc
#:wrap stream-surface-allocator)
(define-cairo cairo_surface_set_user_data
(_fun _cairo_surface_t _pointer _pointer (_fun #:atomic? #t _pointer -> _void)
-> _int))
(define-cairo cairo_ps_surface_set_eps (_fun _cairo_surface_t _bool -> _void)
#:fail (lambda ()
;; cairo_ps_surface_set_eps is in version 1.6 and later;

View File

@ -57,7 +57,12 @@ If @scheme[use-paper-bbox] is @scheme[#f], then the PostScript
When @racket[output] is not @racket[#f], then file-mode output is
written to @racket[output]. If @racket[output] is @racket[#f], then
the destination is determined via @racket[current-ps-setup] or by
prompting the user for a pathname.
prompting the user for a pathname. When @racket[output] is a port,
then data is written to @racket[port] by a thread that is created
with the @racket[post-script-dc%] instance; in case that writing
thread's custodian is shut down, calling @method[dc<%> end-doc]
resumes the port-writing thread with @racket[thread-resume]
and @racket[(current-thread)] as the second argument.
See also @scheme[ps-setup%] and @scheme[current-ps-setup]. The
settings for a particular @scheme[post-script-dc%] object are fixed to