#lang racket/base (require racket/class ffi/unsafe ffi/unsafe/alloc racket/draw/private/dc racket/draw/private/local racket/draw/unsafe/cairo racket/draw/private/record-dc racket/draw/private/bitmap-dc racket/draw/private/ps-setup "../../lock.rkt" "dc.rkt" "types.rkt" "utils.rkt" "const.rkt") (provide (protect-out printer-dc% show-print-setup)) (define _HGLOBAL _pointer) (define-cstruct _PAGESETUPDLG ([lStructSize _DWORD] [hwndOwner _HWND] [hDevMode _HGLOBAL] [hDevNames _HGLOBAL] [Flags _DWORD] [ptPaperSize _POINT] [rtMinMargin _RECT] [rtMargin _RECT] [hInstance _HINSTANCE] [lCustData _LPARAM] [lpfnPageSetupHook _fpointer] [lpfnPagePaintHook _fpointer] [lpPageSetupTemplateName _pointer] [hPageSetupTemplate _HGLOBAL])) (define-cstruct _PRINTDLG ([lStructSize _DWORD] [hwndOwner _HWND] [hDevMode _HGLOBAL] [hDevNames _HGLOBAL] [hDC _HDC] [Flags _DWORD] [nFromPage _WORD] [nToPage _WORD] [nMinPage _WORD] [nMaxPage _WORD] [nCopies _WORD] [hInstance _HINSTANCE] [lCustData _LPARAM] [lpfnPrintHook _fpointer] [lpfnSetupHook _fpointer] [lpPrintTemplateName _pointer] [lpSetupTemplateName _pointer] [hPrintTemplate _HGLOBAL] [hSetupTemplate _HGLOBAL]) #:alignment (if is-win64? #f 2)) (define-cstruct _DOCINFO ([cbSize _int] [lpszDocName _permanent-string/utf-16] [lpszOutput _pointer] [lpszDatatype _pointer] [fwType _DWORD])) (define PD_RETURNDC #x00000100) (define PSD_INTHOUSANDTHSOFINCHES #x00000004) (define PSD_INHUNDREDTHSOFMILLIMETERS #x00000008) (define-comdlg32 PageSetupDlgW (_wfun _PAGESETUPDLG-pointer -> _BOOL)) (define-comdlg32 PrintDlgW (_wfun _PRINTDLG-pointer -> _BOOL)) (define-gdi32 StartDocW (_wfun _HDC _DOCINFO-pointer -> _int)) (define-gdi32 StartPage (_wfun _HDC -> (r : _int) -> (unless (positive? r) (failed 'StartPage)))) (define-gdi32 EndPage (_wfun _HDC -> (r : _int) -> (unless (positive? r) (failed 'EndPage)))) (define-gdi32 EndDoc (_wfun _HDC -> (r : _int) -> (unless (positive? r) (failed 'EndDoc)))) (define-gdi32 GetDeviceCaps (_wfun _HDC _int -> _int)) (define LOGPIXELSX 88) (define LOGPIXELSY 90) (define PHYSICALOFFSETX 112) (define PHYSICALOFFSETY 113) (define needs-delete ((allocator DeleteDC) values)) (define (clone-page-setup p) (let ([new-p (malloc 1 _PAGESETUPDLG)]) (set-cpointer-tag! new-p PAGESETUPDLG-tag) (memcpy new-p 0 p 1 _PAGESETUPDLG) new-p)) (define PSD_RETURNDEFAULT #x00000400) (define PSD_MARGINS #x00000002) (define (show-print-setup parent [just-create? #f]) (let* ([pss (current-ps-setup)] [ps (send pss get-native)]) (atomically (let ([p (malloc 'raw 1 _PAGESETUPDLG)]) (set-cpointer-tag! p PAGESETUPDLG-tag) (if ps (memcpy p 0 ps 1 _PAGESETUPDLG) (begin (memset p 0 1 _PAGESETUPDLG) (set-PAGESETUPDLG-lStructSize! p (ctype-sizeof _PAGESETUPDLG)))) (set-PAGESETUPDLG-Flags! p (bitwise-ior (if just-create? PSD_RETURNDEFAULT 0) PSD_MARGINS)) (set-PAGESETUPDLG-hwndOwner! p (and parent (send parent is-shown?) (send parent get-hwnd))) (let ([r (PageSetupDlgW p)]) (when r (let ([new-p (clone-page-setup p)]) (send pss set-native new-p values))) (free p) ;; FIXME: `r' leaks handles through ;; the hDevModes and hDevNames fields r))))) ;; Pango uses the resolution of the screen to make point<->pixel ;; decisions for all devices (by default). So, we make on drawing unit in ;; a printing context match the relative drawing unit for the screen. (define SCREEN-DPI (let ([hdc (GetDC #f)]) (begin0 (exact->inexact (GetDeviceCaps hdc LOGPIXELSX)) (ReleaseDC #f hdc)))) (define printer-dc% (class (record-dc-mixin (dc-mixin bitmap-dc-backend%)) (init [parent #f]) (define parent-frame parent) (super-make-object (make-object win32-bitmap% 1 1 #f)) (inherit get-recorded-command reset-recording) (define pages null) (define/override (end-page) (set! pages (cons (get-recorded-command) pages)) (reset-recording)) (define page-setup (or (send (current-ps-setup) get-native) (begin (show-print-setup #f #t) (send (current-ps-setup) get-native)))) (define-values (page-width page-height margin-left margin-top) ;; We would like to make the size of the DC match the ;; printable area of the device. Unfortunately, we can't get ;; the printable area until after we get a DC, which is too ;; late for determining a page count (that can depend on the page ;; size). So, we treat the area within the user's chosen margins ;; are the printable area; starting out with PSD_MARGINS with 0 margins ;; and PSD_RETURNDEFAULT seems to fill in the minimum margins, ;; which is probably as good an approximation as any to the value ;; that we want. See also `set-point-scale' below, which has to bridge ;; the printable-area coordinate system and the within-paper-margins ;; coordinate system. (let ([scale (if (zero? (bitwise-and (PAGESETUPDLG-Flags page-setup) PSD_INTHOUSANDTHSOFINCHES)) ;; 100ths of mm (/ SCREEN-DPI (* 10.0 2.54)) ;; 1000ths of in (/ SCREEN-DPI 1000.0))] [r (PAGESETUPDLG-rtMargin page-setup)] [p (PAGESETUPDLG-ptPaperSize page-setup)]) (values (* scale (- (POINT-x p) (RECT-left r) (RECT-right r))) (* scale (- (POINT-y p) (RECT-top r) (RECT-bottom r))) (* scale (RECT-left r)) (* scale (RECT-top r))))) (define/override (get-size) (values page-width page-height)) (define start-doc-message #f) (define/override (start-doc s) (super start-doc s) (set! start-doc-message (and s (string->immutable-string s)))) (define/override (end-doc) (let-values ([(hdc from-page to-page) (atomically (let ([p (malloc 'raw 1 _PRINTDLG)]) (set-cpointer-tag! p PRINTDLG-tag) (memset p 0 1 _PRINTDLG) (set-PRINTDLG-lStructSize! p (ctype-sizeof _PRINTDLG)) (set-PRINTDLG-hwndOwner! p (and parent-frame (send parent-frame is-shown?) (send parent-frame get-hwnd))) (set-PRINTDLG-hDevMode! p (PAGESETUPDLG-hDevMode page-setup)) (set-PRINTDLG-hDevNames! p (PAGESETUPDLG-hDevNames page-setup)) (set-PRINTDLG-Flags! p (bitwise-ior PD_RETURNDC)) (set-PRINTDLG-nFromPage! p 1) (set-PRINTDLG-nToPage! p (length pages)) (set-PRINTDLG-nMinPage! p 1) (set-PRINTDLG-nMaxPage! p (length pages)) (set-PRINTDLG-nCopies! p 1) (let ([r (PrintDlgW p)]) (begin0 (if r (values (needs-delete (PRINTDLG-hDC p)) (PRINTDLG-nFromPage p) (PRINTDLG-nToPage p)) (values #f #f #f)) (free p)))))]) (when hdc (atomically (let ([job (let ([di (make-DOCINFO (ctype-sizeof _DOCINFO) start-doc-message #f #f 0)]) (begin0 (StartDocW hdc di) (when start-doc-message (free (DOCINFO-lpszDocName di)))))]) (when (positive? job) (for ([proc (in-list (reverse pages))] [page-no (in-naturals 1)]) (when (<= from-page page-no to-page) (StartPage hdc) (let* ([s (cairo_win32_printing_surface_create hdc)] [cr (cairo_create s)]) (set-point-scale hdc cr margin-left margin-top) (proc (make-object (class (dc-mixin default-dc-backend%) (super-new) (define/override (init-cr-matrix cr) (set-point-scale hdc cr margin-left margin-top)) (define/override (get-cr) cr)))) (cairo_destroy cr) (cairo_surface_destroy s)) (EndPage hdc))) (EndDoc hdc)) (DeleteDC hdc)))))))) (define (set-point-scale hdc cr margin-left margin-top) (let* ([lpx (GetDeviceCaps hdc LOGPIXELSX)] [lpy (GetDeviceCaps hdc LOGPIXELSY)] [dpx (GetDeviceCaps hdc PHYSICALOFFSETX)] [dpy (GetDeviceCaps hdc PHYSICALOFFSETY)] [lx (/ (if (zero? lpx) 300 lpx) SCREEN-DPI)] [ly (/ (if (zero? lpy) 300 lpy) SCREEN-DPI)]) ;; We declared the size of the page based on paper, ;; while DC reflcts just the printable area for the device, ;; so compensate by shifting (and assume that margins ;; are use appropriately) (cairo_translate cr (- dpx) (- dpy)) ;; Scale to make printer scale simulate screen resolution: (cairo_scale cr lx ly) ;; Shift again to skip over th emargin, which we subtracted ;; from the paper size: (cairo_translate cr margin-left margin-top)))