#lang scribble/manual @(require (for-syntax racket/base) (for-label raart lux/chaos ansi racket/format racket/contract racket/base) racket/sandbox scribble/example) @(define ev (parameterize ([sandbox-output 'string] [sandbox-error-output 'string] [sandbox-memory-limit 50] ;; Need to load the tty-raw extension and we don't ;; know where it is. [sandbox-path-permissions '((execute #rx#""))]) (make-evaluator 'racket/base))) @(ev '(require raart/draw)) @(define-syntax-rule (ex r ...) (examples #:eval ev r ...)) @title{raart: Racket ASCII Art and Interfaces} @author{Jay McCarthy} @defmodule[raart] The @racketmodname[raart] module provides an algebraic model of ASCII that can be used for art, user interfaces, and diagrams. It is comparable to @racketmodname[2htdp/image]. Check out some examples in the @link["https://github.com/jeapostrophe/raart/tree/master/t"]{test} directory in the source. @local-table-of-contents[] @section{Buffers} @defmodule[raart/buffer] When drawing, @racketmodname[raart] renders to a @deftech{buffer}. In almost all circumstances, you should use @racket[make-cached-buffer]. @defproc[(buffer? [x any/c]) boolean?]{Identifiers @tech{buffer}s.} @defproc[(make-output-buffer [#:output output output-port? (current-output-port)]) buffer?]{ A @tech{buffer} that displays to @racket[output]. } @defproc[(make-terminal-buffer [rows exact-nonnegative-integer?] [cols exact-nonnegative-integer?] [#:clear? clear? boolean? #t] [#:output output output-port? (current-output-port)]) buffer?]{ A @tech{buffer} that displays to a terminal of @racket[rows] rows and @racket[cols] columns via the port @racket[output]. If @racket[clear?] is non-false, then the terminal will be cleared before display. } @defproc[(make-cached-buffer [rows exact-nonnegative-integer?] [cols exact-nonnegative-integer?] [#:output output output-port? (current-output-port)]) buffer?]{ A @tech{buffer} that displays to a terminal of @racket[rows] rows and @racket[cols] columns via the port @racket[output], with minimal output to the terminal implemented via client-side caching of the screen content so only updates are output. } @defthing[color/c contract?]{ A contract that recognizes the ASCII colors from the list @racket['(black red green yellow blue magenta cyan white brblack brred brgreen bryellow brblue brmagenta brcyan brwhite)]. The actual color display depends on the terminal configuration. } @defthing[style/c contract?]{ A contract that recognizes the ASCII styles from the list @racket['(normal bold inverse underline)]. The actual font displayed may depend on the terminal configuration. } @section{Drawing} @defmodule[raart/draw] @racketmodname[raart] represents ASCII art algebraically as an abstract @racket[raart?] object. @defproc[(raart? [x any/c]) boolean?]{Identifies ASCII art.} @defproc[(raart-w [x raart?]) exact-nonnegative-integer?]{Returns the width of the art.} @defproc[(raart-h [x raart?]) exact-nonnegative-integer?]{Returns the height of the art.} @defproc[(draw [b buffer?] [r raart?]) void?]{Displays @racket[r] to the @racket[b] buffer.} @defproc[(draw-here [r raart?]) void?]{Displays @racket[r] with a freshly created buffer made with @racket[make-output-buffer].} @defproc[(style [s style/c] [r raart?]) raart?]{@racket[r], except with the style given by @racket[s].} @defproc[(fg [c color/c] [r raart?]) raart?]{@racket[r], except with the foreground color given by @racket[c].} @defproc[(bg [c color/c] [r raart?]) raart?]{@racket[r], except with the background color given by @racket[c].} @defproc[(with-drawing [s (or/c style/c #f)] [fc (or/c color/c #f)] [bc (or/c color/c #f)] [r raart?]) raart?]{Wraps @racket[r] in calls to @racket[style], @racket[fg], and @racket[bg] if @racket[s], @racket[fc], or @racket[bc] (respectively) are provided as non-false.} @defproc[(blank [w exact-nonnegative-integer? 0] [h exact-nonnegative-integer? 0]) raart?]{A blank art of width @racket[w] and height @racket[h]. @ex[(draw-here (blank 2 2)) (draw-here (blank 5 5))] } @defproc[(char [c (and/c char? (not/c char-iso-control?))]) raart?]{An art displaying @racket[c]. @ex[(draw-here (char #\a)) (draw-here (char #\b))]} @defproc[(text [s string?]) raart?]{An art displaying @racket[s], which must not contain any @racket[char-iso-control?] characters. @ex[(draw-here (text "Hello World!"))]} @defproc[(hline [w exact-nonnegative-integer?]) raart?]{A horizontal line of @litchar{-} characters of width @racket[w]. @ex[(draw-here (hline 5))]} @defproc[(vline [h exact-nonnegative-integer?]) raart?]{A vertical line of @litchar{|} characters of height @racket[h]. @ex[(draw-here (vline 3))]} @defthing[halign/c contract?]{A contract for the horizontal alignment modes @racket['(left center right)]. @racket['left] means that the art will be extended with blanks to the right@";" @racket['center] places the blanks equally on both sides@";" and @racket['right] places the blanks to the left.} @defthing[valign/c contract?]{A contract for the vertical alignment modes @racket['(top center bottom)]. @racket['top] means that the art will be extended with blanks below";" @racket['center] places the blanks equally on both sides@";" and @racket['bottom] places the blanks above.} @defproc[(vappend2 [y raart?] [x raart?] [#:halign halign (or/c halign/c #f) #f] [#:reverse? reverse? boolean? #f]) raart?]{ Renders @racket[y] vertically above @racket[x]. (If @racket[reverse?] is true, then the effects are evaluated in the opposite order.) Uses @racket[halign] to determine the horizontal alignment. If @racket[halign] is @racket[#f], then the arts must have the same width. @ex[(draw-here (vappend2 (text "Hello") (text "World"))) (eval:error (draw-here (vappend2 (text "Short") (text "Very Very Long")))) (draw-here (vappend2 (text "Short") (text "Very Very Long") #:halign 'left)) (draw-here (vappend2 (text "Short") (text "Very Very Long") #:halign 'right)) (draw-here (vappend2 (text "Short") (text "Very Very Long") #:halign 'center))]} @defproc[(vappend [y raart?] [x raart?] ... [#:halign halign (or/c halign/c #f) #f] [#:reverse? reverse? boolean? #f]) raart?]{Like @racket[vappend2], but for many arguments. @ex[(draw-here (vappend (text "Short") (text "A Little Medium") (text "Very Very Long") #:halign 'right))]} @defproc[(vappend* [y-and-xs (non-empty-listof raart?)] [#:halign halign (or/c halign/c #f) #f] [#:reverse? reverse? boolean? #f]) raart?]{Like @racket[vappend], but accepts arguments as a list. @ex[(draw-here (vappend* (list (text "Short") (text "A Little Medium") (text "Very Very Long")) #:halign 'right))]} @defproc[(happend2 [y raart?] [x raart?] [#:valign valign (or/c valign/c #f) #f] [#:reverse? reverse? boolean? #f]) raart?]{ Renders @racket[y] horizontally to the left of @racket[x]. (If @racket[reverse?] is true, then the effects are evaluated in the opposite order.) Uses @racket[valign] to determine the vertical alignment. If @racket[valign] is @racket[#f], then the arts must have the same height. @ex[(draw-here (happend2 (vline 2) (vline 2))) (eval:error (draw-here (happend2 (vline 2) (vline 4)))) (draw-here (happend2 (vline 2) (vline 4) #:valign 'top)) (draw-here (happend2 (vline 2) (vline 4) #:valign 'center)) (draw-here (happend2 (vline 2) (vline 4) #:valign 'bottom))]} @defproc[(happend [y raart?] [x raart?] ... [#:valign valign (or/c valign/c #f) #f] [#:reverse? reverse? boolean? #f]) raart?]{Like @racket[happend2], but for many arguments. @ex[(draw-here (happend (vline 2) (vline 3) (vline 4) #:valign 'top))]} @defproc[(happend* [y-and-xs (non-empty-listof raart?)] [#:valign valign (or/c valign/c #f) #f] [#:reverse? reverse? boolean? #f]) raart?]{Like @racket[happend], but accepts arguments as a list. @ex[(draw-here (happend* (list (vline 2) (vline 3) (vline 4)) #:valign 'top))]} @defproc[(para [max-width exact-nonnegative-integer?] [s string?] [#:halign halign halign/c 'left]) raart?]{An art displaying @racket[s], that is at most @racket[max-width] wide, taking multiple lines if necessary. @ex[(draw-here (para 45 "And it came to pass that I, Nephi, said unto my father: I will go and do the things which the Lord hath commanded, for I know that the Lord giveth no commandments unto the children of men, save he shall prepare a way for them that they may accomplish the thing which he commandeth them."))]} @defproc[(para* [max-width exact-nonnegative-integer?] [rs (listof raart?)] [#:halign halign halign/c 'left] [#:gap gap raart? (blank)]) raart?]{Like @racket[happend*], but limits the total width and uses @racket[vappend] when things get too long. @racket[para] uses this after splitting the input string into words and supplies @racket[(text " ")] as the @racket[gap].} @defproc[(place-at [back raart?] [dr exact-nonnegative-integer?] [dh exact-nonnegative-integer?] [front raart?]) raart?]{Renders @racket[front] on top of @racket[back] offset by @racket[dr] rows and @racket[dh] columns.} @defform[(place-at* back [dr dc fore] ...) #:contracts ([back raart?] [dr exact-nonnegative-integer?] [dc exact-nonnegative-integer?] [fore raart?])]{Calls @racket[place-at] on a sequence of art objects from back on the left to front on the right.} @defproc[(frame [#:style s (or/c style/c #f) #f] [#:fg fc (or/c color/c #f)] [#:bg bc (or/c color/c #f)] [x raart?]) raart?]{Renders @racket[x] with a frame where the frame character's style is controlled by @racket[s], @racket[fc], and @racket[bc].} @defproc[(matte-at [mw exact-nonnegative-integer?] [mh exact-nonnegative-integer?] [c exact-nonnegative-integer?] [r exact-nonnegative-integer?] [x raart?]) raart?]{Mattes @racket[x] inside a blank of size @racket[mw] columns and @racket[mh] rows at row @racket[r] and column @racket[c].} @defproc[(translate [dr exact-nonnegative-integer?] [dc exact-nonnegative-integer?] [x raart?]) raart?]{Translates @racket[x] by @racket[dr] rows and @racket[dc] columns.} @defproc[(matte [w exact-nonnegative-integer?] [h exact-nonnegative-integer?] [#:halign halign halign/c 'center] [#:valign valign valign/c 'center] [x raart?]) raart?]{Mattes @racket[x] inside a blank of size @racket[w]x@racket[h] with the given alignment.} @defproc[(inset [dw exact-nonnegative-integer?] [dh exact-nonnegative-integer?] [x raart?]) raart?]{Insets @racket[x] with @racket[dw] columns and @racket[dh] rows of blanks.} @defproc[(mask [mc exact-nonnegative-integer?] [mw exact-nonnegative-integer?] [mr exact-nonnegative-integer?] [mh exact-nonnegative-integer?] [x raart?]) raart?]{Renders the portion of @racket[x] inside the rectangle (@racket[mc],@racket[mr]) to (@racket[(+ mc mw)],@racket[(+ mr mh)]).} @defproc[(crop [cc exact-nonnegative-integer?] [cw exact-nonnegative-integer?] [cr exact-nonnegative-integer?] [ch exact-nonnegative-integer?] [x raart?]) raart?]{Renders the portion of @racket[x] inside the rectangle (@racket[cc],@racket[cr]) to (@racket[(+ cc cw)],@racket[(+ cr ch)]) and removes the surrounding blanks.} @defproc[(table [cells (listof (listof raart?))] [#:frames? frames? boolean? #t] [#:style s (or/c style/c #f) #f] [#:fg f (or/c color/c #f) #f] [#:bg b (or/c color/c #f) #f] [#:inset-dw dw exact-nonnegative-integer? 0] [#:inset-dh dh exact-nonnegative-integer? 0] [#:valign row-valign valign/c 'top] [#:halign halign (or/c halign/c (list*of halign/c (or/c halign/c '()))) 'left]) raart?]{Renders a table of cells where frames are added if @racket[frames?] is non-false with style and color given by the arguments. Cells are inset by @racket[inset-dh] rows and @racket[inset-dw] columns. Cells are horizontally aligned with @racket[halign]. Rows are vertically aligned with @racket[row-valign].} @defproc[(text-rows [cells (listof (listof any/c))]) (listof (listof raart?))]{Transforms a matrix of content into a matrix of art objects, using @racket[~a] composed with @racket[text] if they are not already art objects.} @defproc[(if-drawn [f (-> exact-nonnegative-integer? exact-nonnegative-integer? exact-nonnegative-integer? exact-nonnegative-integer? any)] [x raart?]) raart?]{Renders @racket[x] and if it ends up being displayed, then calls @racket[f] with the actual bounding box, given as a row, column, width, and height.} @defproc[(place-cursor-after [x raart?] [cr exact-nonnegative-integer?] [ch exact-nonnegative-integer?]) raart?]{Renders @racket[x] but places the cursor at row @racket[cr] and column @racket[ch] afterwards.} @defproc[(without-cursor [x raart?]) raart?]{Renders @racket[x], but signals to @racket[draw] to not display the cursor, if this is the art object given to it. (That is, this has no effect if composed with other drawing operations.)} @section{lux integration} @defmodule[raart/lux-chaos] @racketmodname[raart] provides integration with @racketmodname[lux] via the @racketmodname[ansi] module. @defproc[(make-raart [#:mouse? mouse? boolean? #f]) chaos?]{ Returns a @tech[#:doc '(lib "lux/scribblings/lux.scrbl")]{chaos} that manages the terminal. The values that @racket[word-event] is called with are characters or @racket[screen-size-report] structures. If @racket[mouse?] is non-false, then @racket[any-mouse-event], @racket[mouse-focus-event], or @racket[mouse-event] structures may also be provided. The values that @racket[word-output] should return are @racket[raart?] objects. The drawing will use @racket[make-cached-buffer] to optimize the display process. }