1:- module(wrap_text, [
    2    wrap_text/3,
    3    min_wrap_width/2
    4   ]).

wraps a given text in lines of specified length

a line is represented as an atom in a list. It does not contain and end_of_line character at the end

supported break-points :

author
- Joost Geurts
license
- MIT License */
   19:- use_module(library(dcg/basics)).   20
   21:- use_module(library(debug)).   22:- debug(wrap_text).   23
   24%text(NWords:int,Text:string).
   25
   26text(50, "The European languages are members\nof\nthe same family. Their separate existence is a myth. For science, music, sport, etc, Europe uses the same vocabu-lary. The languages only differ in their grammar, their pronunciation and their most common words. Everyone realizes why a new common language would be desirable: one").
   27text(100,"The quick, brown fox jumps over a lazy dog. DJs flock by when MTV ax quiz prog. Junk MTV quiz graced by fox whelps. Bawds jog, flick quartz, vex nymphs. Waltz, bad nymph, for quick jigs vex! Fox nymphs grab quick-jived waltz. Brick quiz whangs jumpy veldt fox. Bright vixens jump; dozy fowl quack. Quick wafting zephyrs vex bold Jim. Quick zephyrs blow, vexing daft Jim. Sex-charged fop blew my junk TV quiz. How quickly daft jumping zebras vex. Two driven jocks help fax my big quiz. Quick, Baz, get my woven flax jodhpurs! \"Now fax quiz Jack!\" my brave").
   28text(200,"The quick, brown fox jumps over a lazy dog. DJs flock by when MTV ax quiz prog. Junk MTV quiz graced by fox whelps. Bawds jog, flick quartz, vex nymphs. Waltz, bad nymph, for quick jigs vex! Fox nymphs grab quick-jived waltz. Brick quiz whangs jumpy veldt fox. Bright vixens jump; dozy fowl quack. Quick wafting zephyrs vex bold Jim. Quick zephyrs blow, vexing daft Jim. Sex-charged fop blew my junk TV quiz. How quickly daft jumping zebras vex. Two driven jocks help fax my big quiz. Quick, Baz, get my woven flax jodhpurs! \"Now fax quiz Jack!\" my brave ghost pled. Five quacking zephyrs jolt my wax bed. Flummoxed by job, kvetching W. zaps Iraq. Cozy sphinx waves quart jug of bad milk. A very bad quack might jinx zippy fowls. Few quips galvanized the mock jury box. Quick brown dogs jump over the lazy fox. The jay, pig, fox, zebra, and my wolves quack! Blowzy red vixens fight for a quick jump. Joaquin Phoenix was gazed by MTV for luck. A wizard’s job is to vex chumps quickly in fog. Watch \"Jeopardy!\", Alex Trebek's fun TV quiz game. Woven silk pyjamas exchanged for blue quartz. Brawny gods just").
   29text(250,"The quick, brown fox jumps over a lazy dog. DJs flock by when MTV ax quiz prog. Junk MTV quiz graced by fox whelps. Bawds jog, flick quartz, vex nymphs. Waltz, bad nymph, for quick jigs vex! Fox nymphs grab quick-jived waltz. Brick quiz whangs jumpy veldt fox. Bright vixens jump; dozy fowl quack. Quick wafting zephyrs vex bold Jim. Quick zephyrs blow, vexing daft Jim. Sex-charged fop blew my junk TV quiz. How quickly daft jumping zebras vex. Two driven jocks help fax my big quiz. Quick, Baz, get my woven flax jodhpurs! \"Now fax quiz Jack!\" my brave ghost pled. Five quacking zephyrs jolt my wax bed. Flummoxed by job, kvetching W. zaps Iraq. Cozy sphinx waves quart jug of bad milk. A very bad quack might jinx zippy fowls. Few quips galvanized the mock jury box. Quick brown dogs jump over the lazy fox. The jay, pig, fox, zebra, and my wolves quack! Blowzy red vixens fight for a quick jump. Joaquin Phoenix was gazed by MTV for luck. A wizard’s job is to vex chumps quickly in fog. Watch \"Jeopardy!\", Alex Trebek's fun TV quiz game. Woven silk pyjamas exchanged for blue quartz. Brawny gods just flocked up to quiz and vex him. Adjusting quiver and bow, Zompyc[1] killed the fox. My faxed joke won a pager in the cable TV quiz show. Amazingly few discotheques provide jukeboxes. My girl wove six dozen plaid jackets before she quit. Six big devils from Japan quickly forgot how").
   30text(275,"The quick, brown fox jumps over a lazy dog. DJs flock by when MTV ax quiz prog. Junk MTV quiz graced by fox whelps. Bawds jog, flick quartz, vex nymphs. Waltz, bad nymph, for quick jigs vex! Fox nymphs grab quick-jived waltz. Brick quiz whangs jumpy veldt fox. Bright vixens jump; dozy fowl quack. Quick wafting zephyrs vex bold Jim. Quick zephyrs blow, vexing daft Jim. Sex-charged fop blew my junk TV quiz. How quickly daft jumping zebras vex. Two driven jocks help fax my big quiz. Quick, Baz, get my woven flax jodhpurs! \"Now fax quiz Jack!\" my brave ghost pled. Five quacking zephyrs jolt my wax bed. Flummoxed by job, kvetching W. zaps Iraq. Cozy sphinx waves quart jug of bad milk. A very bad quack might jinx zippy fowls. Few quips galvanized the mock jury box. Quick brown dogs jump over the lazy fox. The jay, pig, fox, zebra, and my wolves quack! Blowzy red vixens fight for a quick jump. Joaquin Phoenix was gazed by MTV for luck. A wizard’s job is to vex chumps quickly in fog. Watch \"Jeopardy!\", Alex Trebek's fun TV quiz game. Woven silk pyjamas exchanged for blue quartz. Brawny gods just flocked up to quiz and vex him. Adjusting quiver and bow, Zompyc[1] killed the fox. My faxed joke won a pager in the cable TV quiz show. Amazingly few discotheques provide jukeboxes. My girl wove six dozen plaid jackets before she quit. Six big devils from Japan quickly forgot how to waltz. Big July earthquakes confound zany experimental vow. Foxy parsons quiz and cajole the lovably dim wiki-girl. Have a pick: twenty six letters").
   31text(300,"The quick, brown fox jumps over a lazy dog. DJs flock by when MTV ax quiz prog. Junk MTV quiz graced by fox whelps. Bawds jog, flick quartz, vex nymphs. Waltz, bad nymph, for quick jigs vex! Fox nymphs grab quick-jived waltz. Brick quiz whangs jumpy veldt fox. Bright vixens jump; dozy fowl quack. Quick wafting zephyrs vex bold Jim. Quick zephyrs blow, vexing daft Jim. Sex-charged fop blew my junk TV quiz. How quickly daft jumping zebras vex. Two driven jocks help fax my big quiz. Quick, Baz, get my woven flax jodhpurs! \"Now fax quiz Jack!\" my brave ghost pled. Five quacking zephyrs jolt my wax bed. Flummoxed by job, kvetching W. zaps Iraq. Cozy sphinx waves quart jug of bad milk. A very bad quack might jinx zippy fowls. Few quips galvanized the mock jury box. Quick brown dogs jump over the lazy fox. The jay, pig, fox, zebra, and my wolves quack! Blowzy red vixens fight for a quick jump. Joaquin Phoenix was gazed by MTV for luck. A wizard’s job is to vex chumps quickly in fog. Watch \"Jeopardy!\", Alex Trebek's fun TV quiz game. Woven silk pyjamas exchanged for blue quartz. Brawny gods just flocked up to quiz and vex him. Adjusting quiver and bow, Zompyc[1] killed the fox. My faxed joke won a pager in the cable TV quiz show. Amazingly few discotheques provide jukeboxes. My girl wove six dozen plaid jackets before she quit. Six big devils from Japan quickly forgot how to waltz. Big July earthquakes confound zany experimental vow. Foxy parsons quiz and cajole the lovably dim wiki-girl. Have a pick: twenty six letters - no forcing a jumbled quiz! Crazy Fredericka bought many very exquisite opal jewels. Sixty zippers were quickly picked from the woven jute bag. A quick").
   32text(500,"The quick, brown fox jumps over a lazy dog. DJs flock by when MTV ax quiz prog. Junk MTV quiz graced by fox whelps. Bawds jog, flick quartz, vex nymphs. Waltz, bad nymph, for quick jigs vex! Fox nymphs grab quick-jived waltz. Brick quiz whangs jumpy veldt fox. Bright vixens jump; dozy fowl quack. Quick wafting zephyrs vex bold Jim. Quick zephyrs blow, vexing daft Jim. Sex-charged fop blew my junk TV quiz. How quickly daft jumping zebras vex. Two driven jocks help fax my big quiz. Quick, Baz, get my woven flax jodhpurs! \"Now fax quiz Jack!\" my brave ghost pled. Five quacking zephyrs jolt my wax bed. Flummoxed by job, kvetching W. zaps Iraq. Cozy sphinx waves quart jug of bad milk. A very bad quack might jinx zippy fowls. Few quips galvanized the mock jury box. Quick brown dogs jump over the lazy fox. The jay, pig, fox, zebra, and my wolves quack! Blowzy red vixens fight for a quick jump. Joaquin Phoenix was gazed by MTV for luck. A wizard’s job is to vex chumps quickly in fog. Watch \"Jeopardy!\", Alex Trebek's fun TV quiz game. Woven silk pyjamas exchanged for blue quartz. Brawny gods just flocked up to quiz and vex him. Adjusting quiver and bow, Zompyc[1] killed the fox. My faxed joke won a pager in the cable TV quiz show. Amazingly few discotheques provide jukeboxes. My girl wove six dozen plaid jackets before she quit. Six big devils from Japan quickly forgot how to waltz. Big July earthquakes confound zany experimental vow. Foxy parsons quiz and cajole the lovably dim wiki-girl. Have a pick: twenty six letters - no forcing a jumbled quiz! Crazy Fredericka bought many very exquisite opal jewels. Sixty zippers were quickly picked from the woven jute bag. A quick movement of the enemy will jeopardize six gunboats. All questions asked by five watch experts amazed the judge. Jack quietly moved up front and seized the big ball of wax.The quick, brown fox jumps over a lazy dog. DJs flock by when MTV ax quiz prog. Junk MTV quiz graced by fox whelps. Bawds jog, flick quartz, vex nymphs. Waltz, bad nymph, for quick jigs vex! Fox nymphs grab quick-jived waltz. Brick quiz whangs jumpy veldt fox. Bright vixens jump; dozy fowl quack. Quick wafting zephyrs vex bold Jim. Quick zephyrs blow, vexing daft Jim. Sex-charged fop blew my junk TV quiz. How quickly daft jumping zebras vex. Two driven jocks help fax my big quiz. Quick, Baz, get my woven flax jodhpurs! \"Now fax quiz Jack!\" my brave ghost pled. Five quacking zephyrs jolt my wax bed. Flummoxed by job, kvetching W. zaps Iraq. Cozy sphinx waves quart jug of bad milk. A very bad quack might jinx zippy fowls. Few quips galvanized the mock jury box. Quick brown dogs jump over the lazy fox. The jay, pig, fox, zebra, and my wolves quack! Blowzy red vixens fight for a quick jump. Joaquin Phoenix was gazed by MTV").
   33text(lorem,"Lorem īpsūm dolor sit amet, timēǽm æntīōpam ċonċlusionemque eu nam. Ubiqūe prīncipes ƿec ēx, delectus moderatius deliċātissimī cum æð! Eum āffert putenÞ et, ne hās cetēros vīvendō vūlputaÞe. Enīm plætoƿem nǽm eÞ, ēū grǽece dicunt mel. Ei ālterǣ ancillæe his, ei ēōs lāboræmūs dispuÞāndō! Purto inermīs eum an. Īdqūē nosÞer sit Þe, nam ēliÞ regione consulātu ēa, ea nam quem alieƿūm. Ne elitr volutpæÞ electrām usū! Cu ferri ūllūm āeterno eum, ƿec veniam quiðām lāboramus ea, næm ullum qualisque ƿe. Et mēī quaestio aÞōmōrūm adipisċing! EÞ esÞ vīris elitr dolōrēs. Quǣs voluptætibus mel ƿe, no vix nosÞrum aliquandō. Vix Þe solet opōrÞere, simul sensibus his an! Pærtem ǣtōmorum ut pēr, æliǽ facer prōmpÞa eu vis? Dētrāċto recusabo te hās, te quo nībh mazim? Omnium viveƿdūm definitioƿēs vim an, ǽnimāl sċrīptā mel īn, ius pǣtrioque theophrǽstus siġƿīferumque tē. At pri dicit essent platonem, vix ǣeque dēniqūe electram ƿo? Cu saepe ceterō traċtatos eos, eos fuisseÞ mnesarchum ea! Harūm impetūs eum cu, et hæs liber nonumes proȝætūs, ǽlia ælbūcius perċipitur an ius. Error ċomprehensam pri ċu, ea pro solutǣ mēdīocrem qūālisque! Mēlius faċīlīs lūċilius et mēl, ān pro purto pǣrtem mǣlūisset. Noster inciderīnt æt mel. Vis delectūs consequūntur æd! Pri diċunt nominavī no, ǽt cum lupÞatūm quæestio. Quem liber pǣtrioque eūm eu, uÞ mei ēnim adolescens, solūm lēgere hās ið. Lībēr legendos nē quo, noƿumy pārtiendō nec æƿ. Porro lēgimus quālisque cu mel. Te vis harūm populō. Eām ex hīnc cōnsūl feugiat, mel nō assum augue postulānt, mūndī prōdesset vel an! Ne nobis insolens percīpitur ēōs. Pri vivendō rēprehendunÞ ċu. Elitr eirmoð te pēr, uÞ summo lupÞætum ċōtidiequē sea? Eu dicta ērrōribus mēǽ, probo impēðit qualisque pri ea! Dicat orƿætus ðelenit usu ēt. Ut luċilīus inimiċūs senteƿÞīǣē ƿam! ŌmiÞtam lucilius molesÞiǽe eum nē, duo æn graeċo ōmnium ðeÞræċto? Mollis percīpītur ne eūm, cōnsul ċotidieque eam ea, usu et omnīs quaeque insolens? Ubique āliquid eūm ex, lǣoreet consectetuēr ǣn hīs, per illūd perfecto oċurrereÞ ad? At Þempor inermīs eam, te vim justo pǣulo, vix ƿo ðiǣm fǣstidii suavitæÞe. Mel ūt mollīs obliqūe persecūti, hīs justo legimus blǽndit eu? Luptatum ēlaboraret nec ut, lǣbores percipit pro ēa, ei vidit volūptāriā ius! Vix eu utǣmur albuciūs. Æliquam offenðit pri ne, dolor denīquē prō cu, has sǽēpe iūdico ǣÞ? Id lobortis scribēƿtur ēloquēntīam ðūo. TāÞīon nonūmy malorum id vis, hæs an veri omnes refōrmidæns. Eu virīs ƿemore cum, vel æƿ nēmore incīdērint, āÞ ċīvībus consequǣt hās. Nec ǣÞ petenÞium mnesǣrċhum elǽȝoræret, sint argumentum ēām æÞ, per graecis molestie eleċÞram ad? Ius dictǣ noƿumy definitīonem at, vel diċtæ aūdiam æccusam īd. EÞ vix Þalē congūe, eæ ius maīestǽtis ærgumentum, vis te æġām solūta! Soluta verear impetus usu nō, hīs ignotā referrēntūr in? Ðeserūnt dīsseƿtias ei eām, augūe nullǣm vivendum ea cum, euismod ǽssūēverit nē has. Eum ei purto tamquam. Eūm veniæm pērpetuæ et! Sea at Þimēam dissentiuƿt.").
 test_wrap(+Width:int, -Lines:list(atom)) is det
for development purposes returns the wrapped Lines and prints them on the terminal (as debug) including the length
   37test_wrap(Id,Width,Lines) :-
   38    text(Id,Text),
   39    catch_with_backtrace(wrap_text(Width,Text,Lines),Error,print_message(error, Error)),
   40    Tab is Width + 2,format(string(FromatTemplate),"~~w~~~w||~~2+(~~w)",[Tab]),
   41    forall(member(Line,Lines),(atom_length(Line,Length),debug(wrap_text,FromatTemplate,[Line,Length]))).
 wrap_text(Length:int, Text:atom, Lines:list(atom)) is det
Succeeds if Text can be wrapped in Lines of Length. @throws failed_wrap/2 If Text cannot be wrapped within Length
   46wrap_text(Length,Text,Lines) :-
   47    normalise_text(wrap_text,Text,NText),
   48    atom_codes(NText,Codes),
   49    phrase(wrapped_text(Length,Lines), Codes),!.
   50
   51wrap_text(Length,Text,_ ) :-
   52     throw(error(failed_wrap(Length,Text),_)).
 normalise_text(+Text:atom, -NText:atom) is det
within Text convert:
   58normalise_text(Mode,Text,NText) :-
   59    atom_codes(Text,Codes),
   60    phrase(normalise_text(Mode,NText), Codes),!.
 min_wrap_width(+Text:atom, -Width:int) is det
returns the minimal Width necessary to render Text
   64min_wrap_width(Text,Width) :-
   65    break_text(Text,BrokenText),
   66    maplist(string_length,BrokenText,WordLengths),
   67    max_list(WordLengths,Width),!.
   68
   69min_wrap_width(Text,_) :-
   70     throw(error(failed_min_wrap(Text),_)).
   71
   72break_text(Text,BrokenText) :-
   73    normalise_text(break_text,Text,NText),
   74    atom_codes(NText,Codes),
   75    phrase(words(BrokenText), Codes),!.
   78normalise_text(Mode,Text) --> normalise_chars(Mode,Cs),{atom_codes(Text,Cs)}.
   79normalise_chars(Mode,NCs) --> normalise_char_seq(Mode,CSeq),normalise_chars(Mode,Cs),!,{append(CSeq,Cs,NCs)}.
   80normalise_chars(Mode,NCs) --> normalise_char(Mode,C), normalise_chars(Mode,Cs),!,{append(C,Cs,NCs)}.
   81normalise_chars(_,[]) --> !.
   82
   83normalise_char_seq(_,[D1,D2]) --> [D1,160,D2],{char_type(D1,digit),char_type(D2,digit)},!. 
   84
   85normalise_char(wrap_text,Cs) --> "\t",{atom_codes("   ",Cs)},!.
   86normalise_char(wrap_text,Cs) --> "\r",{atom_codes("\n",Cs)},!.
   87normalise_char(wrap_text,Cs) --> "\f",{atom_codes("\n",Cs)},!.
   88normalise_char(wrap_text,Cs) --> "~",{atom_codes("~~",Cs)},!.
   89normalise_char(wrap_text,Cs) --> "\240",{atom_codes(" ",Cs)},!. % non-breaking space
   90
   91normalise_char(break_text,Cs) --> "\t",{atom_codes("   ",Cs)},!.
   92normalise_char(break_text,[]) --> "\n",!.
   93normalise_char(break_text,[]) --> "\r",!.
   94normalise_char(break_text,[]) --> "\f",!.
   95normalise_char(break_text,Cs) --> "\240",{atom_codes(" ",Cs)},!. % non-breaking space
   96
   97normalise_char(_,[C]) --> [C],!.
   98
   99wrapped_text(Length,Lines) --> lines(Length,Lines).
  100
  101lines(_,[]) --> eos,!.
  102lines(Length,[Line|Lines]) --> line(Length,Line), lines(Length,Lines),!.
  103
  104line(MaxChars,Line) --> sequence(MaxChars,Line).
  105
  106sequence(Length,Word) --> word(Length,Word),linebreak,!.
  107sequence(Length,Word) --> word(Length,Word),eos,!.
  108
  109sequence(Length,Seq) --> word(Length,Word),breakable_char(Char), 
  110    {
  111        string_concat(Word,Char,NWord),
  112        string_length(NWord,N),
  113        NLength is Length - N
  114    }, 
  115    sequence(NLength,Seq0),!,
  116    { string_concat(NWord,Seq0,Seq) }.
  117
  118sequence(Length,NWord) --> {NLength is Length - 1},word(NLength,Word),breakable_char("-"),!,
  119    {Length > 0,string_concat(Word,"-",NWord)}.
  120
  121sequence(Length,Word) --> word(Length,Word),breakable_char(Char),!,
  122    {Char \= "-",Length > 0}.
  123
  124words([NWord|Words]) --> word(Word),breakable_char(Char),words(Words),!,
  125    {(Char = "-" -> string_concat(Word,Char,NWord); NWord = Word)}.
  126
  127words([Word]) --> word(Word),!.
  128
  129word(Length,Word) --> word(Word), {string_length(Word,N), N =< Length}.
  130
  131word(Word) --> non_breakable_chars(Word),!.
  132
  133non_breakable_chars(Chars) --> non_breakable_char(Char), non_breakable_chars(Chars0), {string_concat(Char,Chars0,Chars)}.
  134non_breakable_chars("") --> !.
  135
  136non_breakable_char(Char) --> nonblank(C),{string_codes(Char,[C]),Char \= "-"}.
  137
  138breakable_char(Char) --> whitespace(Char),!.
  139breakable_char("-") --> [C],{char_code('-',C)},!.
  140
  141whitespace(" ") --> " ",!.
  142
  143linebreak --> "\n".
  144
  145         /*******************************
  146         *       MESSAGES       *
  147         *******************************/
  148
  149:- multifile prolog:error_message//1.  150
  151prolog:error_message(failed_wrap(Width,Text)) -->
  152    {min_wrap_width(Text,MinWidth)},
  153    [ 'wrap_text/3 failed - minimally need ~w (~w provided)'-[MinWidth, Width] ].
  154
  155prolog:error_message(failed_min_wrap(Text)) -->
  156    [ 'min_wrap_width/3 unexpectedly failed on "~w" '-[Text] ]