racket/collects/wxme/wxme.rkt

723 lines
31 KiB
Racket

(module wxme mzscheme
(require mzlib/port
mzlib/string
mzlib/kw
mzlib/class
racket/contract
mzlib/list
scheme/gui/dynamic
syntax/modread
(only racket/snip/private/snip int->img-type)
"image.ss"
"editor.ss"
"private/compat.ss")
(define (decode who port snip-filter close? skip-content?)
(expect #rx#"^WXME" port who "does not start with \"WXME\"")
(expect #rx#"^01" port who "unrecognized format (not \"01\")")
(let ([vers (string->number
(bytes->string/latin-1
(expect #rx#"^0[1-8]" port who "unrecognized version")))])
(unless (vers . < . 4)
(expect #rx#"^ ##[ \r\n]" port who "missing \" ## \" tag in the expected place"))
(let ([header (read-header who port vers snip-filter skip-content?)])
(let ([port (port->decoded-port who port vers header close?)])
(if skip-content?
(extract-used header port)
port)))))
(define (expect rx port who msg)
(let ([m (regexp-match rx port)])
(unless m
(error who "bad WXME stream; ~a" msg))
(car m)))
(define (extract-used header port)
(let loop ()
(unless (eof-object? (read-byte-or-special port))
(loop)))
(values
(filter values (map (lambda (c)
(and (snip-class-used? c)
(bytes->string/latin-1 (snip-class-name c))))
(vector->list (header-classes header))))
(filter values (map (lambda (c)
(and (data-class-used? c)
(bytes->string/latin-1 (data-class-name c))))
(vector->list (header-data-classes header))))))
;; ----------------------------------------
(define-struct header (classes data-classes styles snip-filter skip-unknown? snips-to-go stream skip-content?))
(define (header-plain-text? h)
(not (header-snip-filter h)))
(define (read-header who port vers snip-filter skip-content?)
(let* ([classes (read-snip-class-list who port vers)]
[data-classes (read-data-class-list who port vers)]
[header (make-header classes
data-classes
(make-hash-table)
snip-filter
(unknown-extensions-skip-enabled)
0
#f
skip-content?)])
(set-header-stream! header (make-object stream% who port vers header))
(let ([cnt (read-editor who port vers header)])
(set-header-snips-to-go! header cnt))
header))
(define (read-editor who port vers header)
(discard-headers/footers who port vers)
(read-styles who port vers (header-styles header))
(read-class-headers who port vers header)
(read-integer who port vers "snip count"))
(define (read-editor-footers who port vers header)
(discard-headers/footers who port vers))
(define (read-editor-snip who port vers header)
(let ([cnt (read-editor who port vers header)])
(let loop ([cnt cnt][accum null])
(if (zero? cnt)
(begin
(read-editor-footers who port vers header)
(if (header-plain-text? header)
(apply bytes-append (reverse accum))
(snip-results->port 'nested-editor
(let ([results (reverse accum)])
(lambda ()
(and (pair? results)
(begin0
(car results)
(set! results (cdr results))))))
void)))
(loop (sub1 cnt)
(cons (read-snip who port vers header)
accum))))))
(define-struct snip-class (name version required? manager used?))
(define (read-snip-class-list who port vers)
(let ([cnt (read-integer who port vers "snip-class count")])
(list->vector
(let loop ([i 0])
(if (= i cnt)
null
(cons
(let ([name (read-a-string who port vers "snip-class name")])
(make-snip-class name
(read-integer who port vers "snip-class version")
(begin (read-integer who port vers "snip-class required?")
;; required? value isn't actually used; only a few
;; built-in classes are required
(member name '(#"wxtext" #"wxtab" #"wxmedia")))
#f
#f))
(loop (add1 i))))))))
(define-struct data-class (name required? manager used?))
(define (read-data-class-list who port vers)
(let ([cnt (read-integer who port vers "data-class count")])
(list->vector
(let loop ([i 0])
(if (= i cnt)
null
(cons
(let ([name (read-a-string who port vers "data-class name")])
(make-data-class name
(equal? name #"wxloc")
#f
#f))
(loop (add1 i))))))))
(define (discard-headers/footers who port vers)
(let ([cnt (read-fixed-integer who port vers "header/footer extension count")])
(let loop ([i 0])
(unless (= i cnt)
(let ([len (read-fixed-integer who port vers "header/footer extension length")])
(skip-data port vers len)
(loop (add1 i)))))))
(define (read-styles who port vers styles)
(let ([id (read-integer who port vers "style-list id")])
(hash-table-get styles id
(lambda ()
(let ([cnt (read-integer who port vers "style count")])
(let loop ([i 1])
(unless (= i cnt)
(unless ((read-integer who port vers "base-style id") . < . i)
(read-error who "integer less than current index" "base-style id" port))
(read-a-string who port vers "style name")
(if (zero? (read-integer who port vers "style is-join?"))
(begin
(read-integer who port vers "style family")
(read-a-string who port vers "style face")
(read-inexact who port vers "style size multiply")
(read-integer who port vers "style size addition")
(read-integer who port vers "style weight on")
(read-integer who port vers "style weight off")
(read-integer who port vers "style slant on")
(read-integer who port vers "style slant off")
(unless (vers . < . 5)
(read-integer who port vers "style smoothing on")
(read-integer who port vers "style smoothing off"))
(read-integer who port vers "style underlined on")
(read-integer who port vers "style underlined off")
(unless (vers . < . 6)
(read-integer who port vers "style size-in-pixels on")
(read-integer who port vers "style size-in-pixels off"))
(unless (vers . < . 3)
(read-integer who port vers "style transparent on")
(read-integer who port vers "style transparent off"))
(read-inexact who port vers "style foreground multiply red")
(read-inexact who port vers "style foreground multiply green")
(read-inexact who port vers "style foreground multiply blue")
(read-inexact who port vers "style background multiply red")
(read-inexact who port vers "style background multiply green")
(read-inexact who port vers "style background multiply blue")
(read-integer who port vers "style foreground addition red")
(read-integer who port vers "style foreground addition green")
(read-integer who port vers "style foreground addition blue")
(read-integer who port vers "style background addition red")
(read-integer who port vers "style background addition green")
(read-integer who port vers "style background addition blue")
(read-integer who port vers "style alignment on")
(read-integer who port vers "style alignment off"))
(unless ((read-integer who port vers "shift-style id") . < . i)
(read-error who "integer less than current index" "shift-style id" port)))
(loop (add1 i)))))
(hash-table-put! styles id id)))))
(define (read-class-headers who port vers header)
(let ([cnt (read-fixed-integer who port vers "class-header count")])
(let loop ([i 0])
(unless (= i cnt)
(let ([pos (read-integer who port vers "class-header class index")]
[len (read-fixed-integer who port vers "class-header length")])
(let ([class (find-class pos header who port vers #f)])
(if (and class
(object? (snip-class-manager class)))
(send (snip-class-manager class) read-header (snip-class-version class) (header-stream header))
(skip-data port vers len)))
(loop (add1 i)))))))
(define (read-snip who port vers header)
(let ([pos (read-integer who port vers "snip class index")])
(let ([class (find-class pos header who port vers #t)])
(let ([len (and (or (not class)
(not (snip-class-required? class)))
(read-fixed-integer who port vers "snip length"))])
(if (and class
(snip-class-manager class)
(not (and len
(header-skip-content? header))))
(let ([style (read-integer who port vers "snip style index")]
[m (snip-class-manager class)]
[cvers (snip-class-version class)])
(let ([s (if (procedure? m)
;; Built-in snip class:
(m who port vers cvers header)
;; Extension snip class:
(let* ([text? (header-plain-text? header)]
[s (send m read-snip
text?
cvers
(header-stream header))])
(if (and text?
(not (bytes? s)))
(error 'read-snip
"reader for ~a in text-only mode produced something other than bytes: ~e"
(snip-class-name class)
s)
s)))])
(read-buffer-data who port vers header)
(if (header-skip-content? header)
#""
(if (bytes? s)
;; Return bytes for the stream:
s
;; Filter the non-bytes result, and then wrap it as
;; a special stream result:
(let ([s ((header-snip-filter header) s)])
(lambda (src line col pos)
(if (s . is-a? . readable<%>)
(send s read-special src line col pos)
s)))))))
(begin
(skip-data port vers len)
#""))))))
(define (read-buffer-data who port vers header)
(let loop ()
(let ([pos (read-integer who port vers "data-class index")])
(unless (zero? pos)
(let ([data-class (find-data-class pos header who port vers #t)])
(let ([len (and (or (not data-class)
(not (data-class-required? data-class)))
(read-fixed-integer who port vers "data length"))])
(if data-class
((data-class-manager data-class) who port vers header)
(skip-data port vers len))))
(loop)))))
;; ----------------------------------------
(define (read-raw-string who port vers what)
(let ([v (cond
[(vers . >= . 8) (plain-read port)]
[else (read-integer who port vers what)])])
(unless (and (integer? v)
(exact? v)
(v . >= . 0))
(read-error who "non-negative exact integer for string length" what port))
(let ([s (cond
[(vers . >= . 8) (plain-read port)]
[else (read-bytes v port)])])
(cond
[(bytes? s)
(unless (= (bytes-length s) v)
(read-error who "byte string whose length matches an integer count" what port))
s]
[(and (list? s)
(andmap bytes? s))
(let ([s (apply bytes-append s)])
(unless (= (bytes-length s) v)
(read-error who "list of byte strings whose total length matches an integer count" what port))
s)]
[else
(read-error who "byte string or byte-string list" what port)]))))
(define (read-a-string who port vers what)
(let ([s (read-raw-string who port vers what)])
(let ([len (bytes-length s)])
(when (zero? len)
(read-error who "non-empty raw string" what port))
(subbytes s 0 (sub1 len)))))
(define (read-integer who port vers what)
(cond
[(vers . >= . 8)
(let ([v (plain-read port)])
(unless (and (integer? v)
(exact? v)
(<= (- (expt 2 31)) v (expt 2 31)))
(read-error who "exact integer between [-2^31,2^31]" what port))
v)]
[else
(let ([b (read-byte port)])
(cond
[(not (zero? (bitwise-and b #x80)))
(cond
[(not (zero? (bitwise-and b #x40)))
(cond
[(bitwise-and b #x01)
(let ([b (read-byte port)])
;; convert to signed:
(if (b . > . 127)
(- b 256)
b))]
[(not (zero? (bitwise-and b #x02)))
(integer-bytes->integer (read-bytes 2 port) #t #t)]
[else
(integer-bytes->integer (read-bytes 4 port) #t #t)])]
[else
(bitwise-ior (arithmetic-shift (bitwise-and #x3F b) 8)
(read-byte port))])]
[else b]))]))
(define (read-fixed-integer who port vers what)
(cond
[(vers . >= . 8)
(read-integer who port vers what)]
[else
(integer-bytes->integer (read-bytes 4 port)
#t
(if (vers . > . 1)
#t
(broken-wxme-big-endian?)))]))
(define (read-inexact who port vers what)
(cond
[(vers . >= . 8)
(let ([v (plain-read port)])
(unless (and (number? v)
(real? v))
(read-error who "real number" what port))
v)]
[else
(floating-point-bytes->real (read-bytes 8 port)
(if (vers . > . 1)
#t
(broken-wxme-big-endian?)))]))
(define (read-error who expected what port)
(error who "WXME format problem while reading for ~a (expected ~a) from port: ~e around position: ~a"
what expected port
(file-position port)))
(define (skip-data port vers len)
(if (vers . >= . 8)
(let loop ([len len])
(unless (zero? len)
(plain-read port)
(loop (sub1 len))))
(read-bytes len port)))
;; ----------------------------------------
(define snip-reader<%>
(interface ()
read-header
read-snip))
(define readable<%>
(interface ()
read-special))
(define (find-class pos header who port vers used?)
(define classes (header-classes header))
(unless (< -1 pos (vector-length classes))
(read-error who "integer less than class-list length" "class index" port))
(let ([class (vector-ref classes pos)])
(when used?
(set-snip-class-used?! class #t))
(unless (snip-class-manager class)
(set-snip-class-manager!
class
(let ([name (snip-class-name class)])
(cond
[(member name '(#"wxtext" #"wxtab"))
(lambda (who port vers cvers header)
(read-integer who port vers "string-snip flags")
(let ([s (read-raw-string who port vers "string-snip content")])
(cond
[(= cvers 1) (string->bytes/utf-8 (bytes->string/latin-1 s))]
[(= cvers 2)
;; UTF-32!
(unless (zero? (remainder (bytes-length s) 4))
(read-error who "size of read byte string is not a multiple of 4" "string-snip content" port))
(let loop ([pos 0][accum null])
(if (= pos (bytes-length s))
(begin
(string->bytes/utf-8 (apply string (reverse accum))))
(loop (+ pos 4)
(cons (integer->char
(let ([v (integer-bytes->integer (subbytes s pos (+ pos 4)) #f
(broken-wxme-big-endian?))])
(unless (or (<= 0 v #xD7FF)
(<= #xE000 v #x10FFFF))
(read-error who "UTF-32 character; probably an endian order mismatch"
"string-snip content" port))
v))
accum))))]
[(cvers . > . 2) s])))]
[(equal? name #"wxmedia")
(lambda (who port vers cvers header)
(read-integer who port vers "nested-editor type")
(read-integer who port vers "nested-editor border")
(read-integer who port vers "nested-editor left margin")
(read-integer who port vers "nested-editor top margin")
(read-integer who port vers "nested-editor right margin")
(read-integer who port vers "nested-editor bottom margin")
(read-integer who port vers "nested-editor left inset")
(read-integer who port vers "nested-editor top inset")
(read-integer who port vers "nested-editor right inset")
(read-integer who port vers "nested-editor bottom inset")
(read-inexact who port vers "nested-editor min width")
(read-inexact who port vers "nested-editor max width")
(read-inexact who port vers "nested-editor min height")
(read-inexact who port vers "nested-editor max height")
(when (cvers . > . 1)
(read-integer who port vers "nested-editor tight-fit?"))
(when (cvers . > . 2)
(read-integer who port vers "nested-editor alignment"))
(when (cvers . > . 3)
(read-integer who port vers "use background color"))
(let ([n (read-editor-snip who port vers header)])
(if (header-plain-text? header)
n
(make-object editor% n))))]
[(equal? name #"wximage")
(lambda (who port vers cvers header)
(let ([filename (read-a-string who port vers "image-snip filename")]
[type (read-integer who port vers "image-snip type")]
[w (read-inexact who port vers "image-snip width")]
[h (read-inexact who port vers "image-snip height")]
[dx (read-inexact who port vers "image-snip x-offset")]
[dy (read-inexact who port vers "image-snip y-offset")]
[relative (read-integer who port vers "image-snip relative?")])
(let ([data
(and (and (equal? filename #"")
(cvers . > . 1)
(not (zero? type)))
;; inlined image
(apply
bytes-append
(let ([len (read-fixed-integer who port vers "image-snip image length")])
(let loop ([i 0])
(if (= i len)
null
(cons
(read-raw-string who port vers "image-snip image content")
(loop (add1 i))))))))])
(if (header-plain-text? header)
#"."
(make-object image%
(if data #f filename)
data w h dx dy
relative (int->img-type type))))))]
[else
(if (header-skip-content? header)
#f
;; Load a manager for this snip class?
(let ([lib (string->lib-path (bytes->string/latin-1 name) #f)])
(if lib
(let ([mgr (dynamic-require lib 'reader)])
(unless (mgr . is-a? . snip-reader<%>)
(error who "reader provided by ~s is not an instance of snip-reader<%>" lib))
mgr)
(if (header-skip-unknown? header)
#f
(error who "cannot load snip-class reader: ~s" name)))))]))))
class))
(define (find-data-class pos header who port vers used?)
(define data-classes (header-data-classes header))
(unless (< -1 pos (vector-length data-classes))
(read-error who "integer less than data-class-list length" "data-class index" port))
(let ([data-class (vector-ref data-classes pos)])
(when used?
(set-data-class-used?! data-class #t))
(unless (data-class-manager data-class)
(set-data-class-manager!
data-class
(case (data-class-name data-class)
[(#"wxloc")
(lambda (who port vers header)
(read-inexact who port vers "location x")
(read-inexact who port vers "location y"))]
[else
(if (header-skip-content? header)
#f
;; Load a manager for this data class?
(error who "cannot load data-class managers, yet"))])))
data-class))
(define stream%
(class object%
(init-field who port vers header)
(public [rfi read-fixed-integer])
(define (rfi what)
(read-fixed-integer who port vers what))
(public [ri read-integer])
(define (ri what)
(read-integer who port vers what))
(public [rix read-inexact])
(define (rix what)
(read-inexact who port vers what))
(define/public (read-raw-bytes what)
(read-raw-string who port vers what))
(define/public (read-bytes what)
(read-a-string who port vers what))
(public [rne read-editor-snip])
(define (rne what)
(read-editor-snip who port vers header))
(super-new)))
(define stream<%> (class->interface stream%))
;; ----------------------------------------
(define lib-mapping (make-hash-table 'equal))
(define (ok-string-element? m)
(and (string? m)
(regexp-match? #rx"^[-a-zA-Z0-9_. ]+$" m)
(not (string=? m ".."))
(not (string=? m "."))))
(define (ok-lib-path? m)
(and (pair? m)
(eq? 'lib (car m))
(pair? (cdr m))
(list? m)
(andmap ok-string-element? (cdr m))))
(define (register-lib-mapping! str target)
(unless (ok-lib-path? target)
(error 'register-lib-mapping! "given target is not a valid marshalable lib path: ~s" target))
(hash-table-put! lib-mapping str target))
(define (string->lib-path str gui?)
(or (let ([m (and (regexp-match #rx"^[(].*[)]$" str)
(let* ([p (open-input-string str)]
[m (read p)])
(and (eof-object? (read p))
m)))])
(or (and (and (list? m)
(= (length m) 2)
(ok-lib-path? (car m))
(ok-lib-path? (cadr m)))
(if gui?
(car m)
(cadr m)))
(and (and (ok-lib-path? m)
gui?)
m)))
(and (not gui?)
(hash-table-get lib-mapping str #f))))
(register-compatibility-mappings! register-lib-mapping!)
;; ----------------------------------------
(define plain-params
(with-module-reading-parameterization
(lambda ()
(current-parameterization))))
(define (plain-read port)
(call-with-parameterization
plain-params
(lambda ()
(with-handlers ([exn:fail:read? (lambda (x) 'no-good)])
(read port)))))
;; ----------------------------------------
(define (port->decoded-port who port vers header close?)
(snip-results->port
(object-name port)
(lambda ()
(let ([snips-to-go (header-snips-to-go header)])
(cond
[(zero? snips-to-go)
(read-editor-footers who port vers header)
#f]
[else
(set-header-snips-to-go! header (sub1 snips-to-go))
(read-snip who port vers header)])))
(if close?
(lambda () (close-input-port port))
void)))
(define (snip-results->port name next-item! close)
(define-values (r w) (make-pipe))
(define (read-proc buffer)
(if (char-ready? r)
(read-bytes-avail! buffer r)
(let ([s (next-item!)])
(cond
[(not s)
(close-output-port w)
eof]
[(bytes? s)
(write-bytes s w)
(read-proc buffer)]
[else s]))))
(make-input-port/read-to-peek
name
read-proc
#f
close))
;; ----------------------------------------
(define (skip-reader port)
(regexp-match/fail-without-reading #rx#"^#reader[(]lib\"read.ss\"\"wxme\"[)]" port))
(define (wxme-convert-port port close? snip-filter)
;; read optional #reader header:
(skip-reader port)
;; decode:
(decode 'read-bytes port snip-filter close? #f))
;; ----------------------------------------
(define unknown-extensions-skip-enabled (make-parameter #f))
(define broken-wxme-big-endian? (make-parameter (system-big-endian?)))
(define (is-wxme-stream? p)
(and (regexp-match-peek #rx#"^(?:#reader[(]lib\"read[.]ss\"\"wxme\"[)])?WXME01[0-9][0-9] ##[ \r\n]" p)
#t))
(define/kw (wxme-port->port port #:optional [close? #t] [snip-filter (lambda (x) x)])
(wxme-convert-port port close? snip-filter))
(define/kw (wxme-port->text-port port #:optional [close? #t])
(wxme-convert-port port close? #f))
(define (do-read orig-port who read)
(let ([port (if (gui-available?)
;; GUI mode, since GRacket is available:
(let ([text% (dynamic-require 'mred 'text%)]
[open-input-text-editor (dynamic-require 'mred 'open-input-text-editor)])
(let ([t (new text%)])
(send t insert-port orig-port 'standard)
(open-input-text-editor t 0 'end values (object-name orig-port) #t)))
;; Non-GUI mode:
(decode who orig-port (lambda (x) x) #f #f))])
;; Turn on line counting if it was on before:
(let-values ([(line col pos) (port-next-location orig-port)])
(when line (port-count-lines! port)))
;; Read:
(let ([v (read port)])
(let ([v2 (let loop ()
(let ([v2 (read port)])
(if (special-comment? v2)
(loop)
v2)))])
(if (eof-object? v2)
v
`(begin
,@(list* v v2 (let loop ([accum null])
(let ([v (read port)])
(cond
[(eof-object? v) (reverse accum)]
[(special-comment? v) (loop accum)]
[else (loop (cons v accum))]))))))))))
(define (wxme-read port)
(do-read port 'read read))
(define (wxme-read-syntax source-name-v port)
(datum->syntax-object
#f
(do-read port 'read-syntax
(lambda (port)
(read-syntax source-name-v port)))))
(define (extract-used-classes port)
(if (is-wxme-stream? port)
(begin
(skip-reader port)
(decode 'extract-used-classes port (lambda (x) x) #f #t))
(values null null)))
(provide/contract [is-wxme-stream? (input-port? . -> . any)]
[wxme-port->text-port (->* (input-port?) (any/c) input-port?)]
[wxme-port->port (->* (input-port?) (any/c (any/c . -> . any)) input-port?)]
[register-lib-mapping! (string? string? . -> . void?)]
[string->lib-path (string? any/c . -> . any)]
[extract-used-classes (input-port? . -> . any)])
(provide unknown-extensions-skip-enabled
broken-wxme-big-endian?
snip-reader<%>
readable<%>
stream<%>
wxme-read
wxme-read-syntax))