diff --git a/collects/net/gifwrite.ss b/collects/net/gifwrite.ss index 37d8c6e99d..7047b4779a 100644 --- a/collects/net/gifwrite.ss +++ b/collects/net/gifwrite.ss @@ -14,25 +14,49 @@ *****************************************************************************/ |# -(module gifwrite mzscheme - (require mzlib/contract) +#reader scribble/reader +(module gifwrite scheme/base + (require scheme/contract + scribble/srcdoc) + + (require/doc scheme/base + scribble/manual) (define LZ_MAX_CODE 4095) (define GifVersionPrefix #"GIF89a") - (provide gif-stream? - image-ready-gif-stream? - image-or-control-ready-gif-stream? - empty-gif-stream? - (rename color-map? gif-colormap?)) + (provide/doc + [gif-stream? ([v any/c] . -> . boolean?) + @{Returns @scheme[@t] if @scheme[v] is a GIF stream created by @scheme[gif-write], + @scheme[#f] otherwise.}] + [image-ready-gif-stream? ([v any/c] . -> . boolean?) + @{Returns @scheme[#t] if @scheme[v] is a GIF stream that is not in + @scheme['done] mode, @scheme[#f] otherwise.}] + [image-or-control-ready-gif-stream? ([v any/c] . -> . boolean?) + @{Returns @scheme[#t] if @scheme[v] is a GIF stream that is in + @scheme['init] or @scheme['image-or-control] mode, @scheme[#f] otherwise.}] + [empty-gif-stream? ([v any/c] . -> . boolean?) + @{Returns @scheme[#t] if @scheme[v] is a GIF stream that in + @scheme['init] mode, @scheme[#f] otherwise.}] + [gif-colormap? ([v any/c] . -> . boolean?) + @{Returns @scheme[#t] if @scheme[v] represets a colormap, @scheme[#f] otherwise. + A colormap is a list whose size is a power of @math{2} between @math{2^1} and @math{2^8}, + and whose elements are vectors of size 3 containing colors + (i.e., exact integers between @math{0} and @math{255} inclusive).}] + [color? ([v any/c]. -> . boolean?) + @{The same as @scheme[byte?].}] + [dimension? ([v any/c]. -> . boolean?) + @{Returns @scheme[#t] if @scheme[v] is an exact integer between + @scheme[#x0] and @scheme[#xFFFF] inclusive, @scheme[#f] + otherwise.}]) (define-struct gif-stream (port SWidth SHeight SBackGroundColor SColorMap - FileState)) + [FileState #:mutable])) (define (image-ready-gif-stream? g) (and (gif-stream? g) @@ -48,10 +72,8 @@ (define color? byte?) (define (dimension? x) (and (exact? x) (integer? x) (<= 0 x #xFFFF))) - (define (disposal? s) - (memq s '(any keep restore-bg restore-prev))) - (define (color-map? l) + (define (gif-colormap? l) (and (list? l) (member (length l) '(2 4 8 16 32 64 128 256)) (andmap (lambda (c) @@ -82,8 +104,8 @@ (define (WRITE g bytes) (write-bytes bytes (gif-stream-port g))) - (provide/contract [gif-state - (gif-stream? . -> . symbol?)]) + (provide/doc [gif-state ([stream gif-stream?] . -> . symbol?) + @{Returns the state of @scheme[stream].}]) (define (gif-state GifFile) (gif-stream-FileState GifFile)) @@ -91,13 +113,27 @@ ;; * This routine should be called before any other EGif calls, immediately ;; * follows the GIF file openning. ;; *****************************************************************************/ - (provide/contract [gif-start - (output-port? - dimension? - dimension? - color? - (or/c false/c color-map?) - . -> . gif-stream?)]) + (provide/doc [gif-start + ([out output-port?] + [w dimension?] + [h dimension?] + [bg-color color?] + [cmap (or/c false/c gif-colormap?)] + . -> . gif-stream?) + @{Writes the start of a GIF file to the given output port, and returns + a GIF stream that adds to the output port. + + The width and height determine a virtual space for the overall GIF + image. Individual images added to the GIF stream must fit within this + virtual space. The space is initialized by the given background color. + + Finally, the default meaning of color numbers (such as the background color) + is determined by the given colormap, but individual images + within the GIF file can have their own colormaps. + + A global colormap need not be supplied, in which case a colormap must + be supplied for each image. Beware that the bg-color is ill-defined if + a global colormap is not provided.}]) (define (gif-start port Width Height @@ -150,16 +186,45 @@ ;; * This routine should be called before any attempt to dump an image - any ;; * call to any of the pixel dump routines. ;; *****************************************************************************/ - (provide/contract [gif-add-image - (image-ready-gif-stream? - dimension? - dimension? - dimension? - dimension? - any/c - (or/c false/c color-map?) - bytes? - . -> . any)]) + (provide/doc [gif-add-image + ([stream image-ready-gif-stream?] + [left dimension?] + [top dimension?] + [width dimension?] + [height dimension?] + [interlaced? any/c] + [cmap (or/c false/c gif-colormap?)] + [bstr bytes?] + . -> . void?) + @{Writes an image to the given GIF stream. The @scheme[left], @scheme[top], + @scheme[width], and @scheme[height] values specify the location and + size of the image within the overall GIF image's virtual space. + + If @scheme[interlaced?] is true, then @scheme[bstr] should provide bytes + ininterlaced order instead of top-to-bottom order. Interlaced order is: + + @(itemize + @item{every 8th row, starting with 0} + @item{every 8th row, starting with 4} + @item{every 4th row, starting with 2} + @item{every 2nd row, starting with 1}) + + If a global color is provided with @scheme[gif-start], a @scheme[#f] value + can be provided for @scheme[cmap]. + + The @scheme[bstr] argument specifies the pixel content of the image. Each + byte specifies a color (i.e., an index in the colormap). Each row is + provided left-to-right, and the rows provided either top-to-bottom or + in interlaced order (see above). If the image is prefixed with a + control that specifies an transparent index (see @scheme[gif-add-control]), + then the corresponding ``color'' doesn't draw into the overall GIF + image. + + An exception is raised if any byte value in @scheme[bstr] is larger than the + colormap's length, if the @scheme[bstr] length is not @scheme[width] times + @scheme[height], or if the @scheme[top], @scheme[left], @scheme[width], and + @scheme[height] dimensions specify a region beyond the overall GIF image's + virtual space.}]) (define (gif-add-image GifFile Left Top @@ -228,13 +293,47 @@ ;;/****************************************************************************** ;; * This routine should be called to add graphic control before the next image ;; *****************************************************************************/ - (provide/contract [gif-add-control - (image-or-control-ready-gif-stream? - disposal? - any/c - dimension? - (or/c false/c color?) - . -> . any)]) + (provide/doc [gif-add-control + ([stream image-or-control-ready-gif-stream?] + [disposal (one-of/c 'keep 'restore-bg 'restore-prev)] + [wait-for-input? any/c] + [delay dimension?] + [transparent (or/c false/c color?)] + . -> . any) + @{Writes an image-control command to a GIF stream. Such a control must + appear just before an image, and it applies to the following image. + + The GIF image model involves processing images one by one, placing + each image into the specified position within the overall image's + virtual space. An image-control command can specify a delay before an + image is added (to create animated GIFs), and it also specifies how + the image should be kept or removed from the overall image before + proceeding to the next one (also for GIF animation). + + The @scheme[disposal] argument specifies how to proceed: + + @(itemize + @item{@scheme['any] : doesn't matter (perhaps because the next image + completely overwrites the current one)} + @item{@scheme['keep] : leave the image in place} + @item{@scheme['restore-bg] : replace the image with the background color} + @item{@scheme['restore-prev] : restore the overall image content to the + content before the image is added}) + + If @scheme[wait-for-input?] is true, then the display program may wait for + some cue from the user (perhaps a mouse click) before adding the + image. + + The @scheme[delay] argument specifies a delay in 1/100s of a + second. + + If the @scheme[transparent] argument is a color, then it + determines an index that is used to represent transparent pixels in the + follow image (as opposed to the color specified by the colormap for the index). + + An exception is raised if a control is already added to @scheme[stream] + without a corresponding image.}]) + (define (gif-add-control GifFile Disposal UserInput? @@ -260,10 +359,17 @@ ;; * This routine should be called to add the "loop" graphic control ;; before adding any images ;; *****************************************************************************/ - (provide/contract [gif-add-loop-control - (empty-gif-stream? - dimension? - . -> . any)]) + (provide/doc [gif-add-loop-control + ([stream empty-gif-stream?] + [iteration dimension?] + . -> . any) + @{Writes a control command to a GIF stream for which no images or other + commands have already been written. The command causes the animating + sequence of images in the GIF to be repeated `iteration-dimension' + times, where 0 can be used to mean ``infinity.'' + + An exception is raise if some control or image has been added to the + stream already.}]) (define (gif-add-loop-control GifFile Iterations) (WRITE GifFile #"\x21\xFF\x0BNETSCAPE2.0\x03\x01") @@ -274,10 +380,14 @@ ;;/****************************************************************************** ;; * This routine should be called to add arbitrary comment text ;; *****************************************************************************/ - (provide/contract [gif-add-comment - (image-or-control-ready-gif-stream? - bytes? - . -> . any)]) + (provide/doc [gif-add-comment + ([stream image-or-control-ready-gif-stream?] + [bstr bytes?] + . -> . any) + @{Adds a generic comment to the GIF stream. + + An exception is raised if an image-control command was just written to + the stream (so that an image is required next).}]) (define (gif-add-comment GifFile Str) (WRITE GifFile #"\x21\xFE") @@ -293,9 +403,15 @@ ;;/****************************************************************************** ;; * This routine should be called last, to end GIF file. ;; *****************************************************************************/ - (provide/contract [gif-end - (image-or-control-ready-gif-stream? - . -> . any)]) + (provide/doc [gif-end + ([stream image-or-control-ready-gif-stream?] + . -> . any) + @{Finishes + writing a GIF file. The GIF stream's output port is not + automatically closed. + + An exception is raised if an image-control command was just written to + the stream (so that an image is required next).}]) (define (gif-end GifFile) (WRITE GifFile #";") (set-gif-stream-FileState! GifFile 'done)) @@ -439,11 +555,37 @@ (and (bytes? b) (zero? (remainder (bytes-length b) 4)))) - (provide/contract - [quantize ((argb-bytes?) - . ->* . - (bytes? color-map? (or/c false/c color?)))]) + (provide/doc + [quantize ([bstr argb-bytes?] + . -> . + (values bytes? gif-colormap? (or/c false/c color?))) + @{Each + image in a GIF stream is limited to 256 colors, including the + transparent ``color,'' if any. The @scheme[quantize] function helps converts a + 24-bit image (plus alpha channel) into an indexed-color image,reducing + the number of colors if necessary. + Given a set of pixels expressed in ARGB format + (i.e., each four bytes is a set of values for one pixel: alpha, red, blue, and green), + @scheme[quantize] produces produces + + @(itemize + @item{bytes for the image (i.e., a array of colors, expressed as a byte string)} + @item{a colormap} + @item{either @scheme[#f] or a color index for the transparent ``color''}) + + The conversion treats alpha values less than 128 as transparent + pixels, and other alpha values as solid. + + The quantization process first attempts to use all (non-transparent) + colors in the image. if that fails, it reduces the image to 12-bit + color (3 bits per each of red, green, and blue) by rounding up pixel + values, and tries again. If that fails, it reduces the image to 6-bit + color (2 bits per each of red, green, and blue). + + To convert a collection of images all with the same quantization, + simply append them for the input of a single call of @scheme[quantize], and + then break apart the result bytes.}]) (define (quantize argb) (let* ([len (quotient (bytes-length argb) 4)] [result (make-bytes len)] diff --git a/collects/net/scribblings/gifwrite.scrbl b/collects/net/scribblings/gifwrite.scrbl new file mode 100644 index 0000000000..85bbbe21f6 --- /dev/null +++ b/collects/net/scribblings/gifwrite.scrbl @@ -0,0 +1,36 @@ +#lang scribble/doc +@(require scribble/manual + scribble/extract + (for-label net/gifwrite)) + +@title{Writing GIF Files} + +@defmodule[net/gifwrite] + +The @schememodname[net/gifwrite] library provides functions for +writing GIF files to a stream, including GIF files with multiple +images and controls (such as animated GIFs). + +A GIF stream is created by @scheme[gif-start], and then individual +images are written with @scheme[gif-add-image]. Optionally, +@scheme[gif-add-control] inserts instructions for rendering the +images. The @scheme[gif-end] function ends the GIF stream. + +A GIF stream can be in any one of the following states: + +@itemize{ + + @item{@scheme['init] : no images or controls have been added to the + stream} + + @item{@scheme['image-or-control] : another image or control can be + written} + + @item{@scheme['image] : another image can be written (but not a + control, since a control was written)} + + @item{@scheme['done] : nothing more can be added} + +} + +@(include-extracted "../gifwrite.ss") diff --git a/collects/net/scribblings/net.scrbl b/collects/net/scribblings/net.scrbl index 694460d437..ba8a705555 100644 --- a/collects/net/scribblings/net.scrbl +++ b/collects/net/scribblings/net.scrbl @@ -10,6 +10,7 @@ @include-section["smtp.scrbl"] @include-section["cgi.scrbl"] @include-section["sendmail.scrbl"] +@include-section["gifwrite.scrbl"] @(bibliography diff --git a/collects/scheme/private/modbeg.ss b/collects/scheme/private/modbeg.ss index 108831d6d0..20d9eb8b52 100644 --- a/collects/scheme/private/modbeg.ss +++ b/collects/scheme/private/modbeg.ss @@ -101,7 +101,20 @@ #%module-begin #%require #%provide)))) #f - #t) + ;; Also check for calls to `void': + (if (free-identifier=? a (quote-syntax #%app)) + (let-values ([(e) (cdr e)]) + (let-values ([(e) (if (syntax? e) + (syntax-e e) + e)]) + (if (pair? e) + (if (symbol? (syntax-e (car e))) + (if (free-identifier=? (car e) (quote-syntax void)) + #f + #t) + #t) + #t))) + #t)) #t)) #t))]) (let-values ([(e) (if wrap? diff --git a/collects/scribble/doclang.ss b/collects/scribble/doclang.ss index f071b79abd..7f2691156f 100644 --- a/collects/scribble/doclang.ss +++ b/collects/scribble/doclang.ss @@ -42,8 +42,10 @@ (append (kernel-form-identifier-list) (syntax->list #'(provide - require))))]) - (syntax-case expanded (begin) + require + #%provide + #%require))))]) + (syntax-case expanded (begin) [(begin body1 ...) #`(doc-begin m-id exprs body1 ... . body)] [(id . rest) @@ -53,7 +55,9 @@ provide define-values define-syntaxes - define-for-syntaxes)))) + define-for-syntaxes + #%require + #%provide)))) #`(begin #,expanded (doc-begin m-id exprs . body))] [_else #`(doc-begin m-id (#,expanded . exprs) . body)])))]))]))) diff --git a/collects/scribble/extract.ss b/collects/scribble/extract.ss new file mode 100644 index 0000000000..de0896b59d --- /dev/null +++ b/collects/scribble/extract.ss @@ -0,0 +1,72 @@ +#lang scheme/base + +(require scribble/manual + scribble/decode + scribble/srcdoc + (for-syntax scheme/base + syntax/path-spec)) + +(provide include-extracted) + +(define-for-syntax (strip-context c) + (cond + [(syntax? c) (datum->syntax + #f + (strip-context (syntax-e c)) + c)] + [(pair? c) (cons (strip-context (car c)) + (strip-context (cdr c)))] + [else c])) + +(define-syntax (include-extracted stx) + (syntax-case stx () + [(_ orig-path) + (let ([path (resolve-path-spec #'orig-path #'orig-path stx)]) + (let ([s-exp + (parameterize ([current-namespace (make-base-namespace)] + [read-accept-reader #t]) + (expand + (with-input-from-file path + (lambda () + (port-count-lines! (current-input-port)) + (read-syntax path)))))]) + (syntax-case s-exp () + [(mod name lang + (mod-beg + content ...)) + (with-syntax ([(content ...) + (map + strip-context + (apply + append + (map (lambda (c) + (syntax-case c (#%plain-app void quote-syntax provide/doc) + [(#%plain-app void (quote-syntax (provide/doc spec ...))) + (syntax->list #'(spec ...))] + [_ null])) + (syntax->list #'(content ...)))))] + [(req ...) + (map + strip-context + (apply + append + (map (lambda (c) + (syntax-case c (#%require #%plain-app void quote-syntax require/doc) + [(#%require spec ...) + (syntax->list #'((for-label spec) ...))] + [(#%plain-app void (quote-syntax (require/doc spec ...))) + (syntax->list #'(spec ...))] + [_ null])) + (syntax->list #'(content ...)))))]) + #`(begin + (#%require (for-label #,(strip-context #'lang)) + (for-label #,(strip-context #'orig-path)) + req ...) + (def-it content) ...))])))])) + +(define-syntax def-it + (syntax-rules () + [(_ ((rename old-id id) contract desc)) + (def-it (id contract desc))] + [(_ (id (-> arg ... result) desc)) + (defproc (id arg ...) result . desc)])) diff --git a/collects/scribble/manual.ss b/collects/scribble/manual.ss index 0aab083821..cff0353346 100644 --- a/collects/scribble/manual.ss +++ b/collects/scribble/manual.ss @@ -1892,6 +1892,12 @@ (list (make-element 'subscript (loop (caddr m)))) (loop (cadddr m))))] + [(regexp-match #px"^(.*)\\^([a-zA-Z0-9]+)(.*)$" i) + => (lambda (m) + (append (loop (cadr m)) + (list (make-element 'superscript + (loop (caddr m)))) + (loop (cadddr m))))] [(regexp-match #px"^(.*)([()0-9{}\\[\\]])(.*)$" i) => (lambda (m) (append (loop (cadr m)) diff --git a/collects/scribble/srcdoc.ss b/collects/scribble/srcdoc.ss new file mode 100644 index 0000000000..1f5e2c2ba6 --- /dev/null +++ b/collects/scribble/srcdoc.ss @@ -0,0 +1,27 @@ +#lang scheme/base + +(require scheme/contract) + +(provide require/doc + provide/doc) + +(define-syntax-rule (require/doc spec ...) + (void (quote-syntax (require/doc spec ...)))) + +(define-syntax-rule (provide/doc [id contract desc] ...) + (begin + (void (quote-syntax (provide/doc [id contract desc] ...))) + (provide/contracted [id (strip-names contract)]) ...)) + +(define-syntax provide/contracted + (syntax-rules (->) + [(_ [(rename orig-id new-id) contract]) + (provide/contract (rename orig-id new-id contract))] + [(_ [id contract]) + (provide/contract [id contract])])) + +(define-syntax strip-names + (syntax-rules (->) + [(_ (-> [id contract] ... result)) + (-> contract ... result)] + [(_ other) other]))