USING: kernel sequences math arrays locals fry accessors splitting make combinators.short-circuit namespaces grouping splitting.monotonic ; IN: wrap word : word-length ( word -- n ) [ black>> ] [ white>> ] bi + ; TUPLE: cons cdr car ; ! This order works out better C: cons : >cons< ( cons -- cdr car ) [ cdr>> ] [ car>> ] bi ; : list-each ( list quot -- ) over [ [ [ car>> ] dip call ] [ [ cdr>> ] dip list-each ] 2bi ] [ 2drop ] if ; inline recursive : singleton? ( list -- ? ) { [ ] [ cdr>> not ] } 1&& ; : ( elt -- list ) f swap ; : list>array ( list -- array ) [ [ , ] list-each ] { } make ; : lists>arrays ( lists -- arrays ) [ [ list>array , ] list-each ] { } make ; TUPLE: paragraph lines head-width tail-cost ; C: paragraph SYMBOL: line-max SYMBOL: line-ideal : deviation ( length -- n ) line-ideal get - sq ; : top-fits? ( paragraph -- ? ) [ head-width>> ] [ lines>> singleton? line-ideal line-max ? get ] bi <= ; : fits? ( paragraph -- ? ) ! Make this not count spaces at end { [ lines>> car>> singleton? ] [ top-fits? ] } 1|| ; :: min-by ( seq quot -- elt ) f 1.0/0.0 seq [| key value new | new quot call :> newvalue newvalue value < [ new newvalue ] [ key value ] if ] each drop ; inline : paragraph-cost ( paragraph -- cost ) [ head-width>> deviation ] [ tail-cost>> ] bi + ; : min-cost ( paragraphs -- paragraph ) [ paragraph-cost ] min-by ; : new-line ( paragraph word -- paragraph ) [ [ lines>> ] [ ] bi* ] [ nip black>> ] [ drop paragraph-cost ] 2tri ; : glue ( paragraph word -- paragraph ) [ [ lines>> >cons< ] dip ] [ [ head-width>> ] [ word-length ] bi* + ] [ drop tail-cost>> ] 2tri ; : wrap-step ( paragraphs word -- paragraphs ) [ '[ _ glue ] map ] [ [ min-cost ] dip new-line ] 2bi prefix [ fits? ] filter ; : 1paragraph ( word -- paragraph ) [ ] [ black>> ] bi 0 ; : post-process ( paragraph -- array ) lines>> lists>arrays [ [ contents>> ] map ] map ; : initialize ( words -- words paragraph ) unclip-slice 1paragraph 1array ; : wrap ( words line-max line-ideal -- paragraph ) [ line-ideal set line-max set initialize [ wrap-step ] reduce min-cost post-process ] with-scope ; PRIVATE> TUPLE: element key width break? ; C: element > ] map sum ; : make-word ( whites blacks -- word ) [ append ] [ [ elements-length ] bi@ ] 2bi ; : ?first2 ( seq -- first/f second/f ) [ 0 swap ?nth ] [ 1 swap ?nth ] bi ; : split-elements ( seq -- half-words ) [ [ break?>> ] bi@ = ] monotonic-split ; : ?first-break ( seq -- newseq f/word ) dup first first break?>> [ unclip-slice f swap make-word ] [ f ] if ; : make-words ( seq f/word -- words ) [ 2 [ ?first2 make-word ] map ] dip [ prefix ] when* ; : elements>words ( seq -- newseq ) split-elements ?first-break make-words ; PRIVATE> : wrap-elements ( elements line-max line-ideal -- lines ) [ elements>words ] 2dip wrap [ concat ] map ; ] map ] map ; : join-words ( wrapped-lines -- lines ) [ " " join ] map ; : join-lines ( strings -- string ) "\n" join ; PRIVATE> : wrap-lines ( lines width -- newlines ) [ split-lines ] dip '[ _ dup wrap join-words ] map concat ; : wrap-string ( string width -- newstring ) wrap-lines join-lines ; : wrap-indented-string ( string width indent -- newstring ) [ length - wrap-lines ] keep '[ _ prepend ] map join-lines ;