Jump to content

Word wrap: Difference between revisions

Add source for Rust
(Add source for Rust)
Line 189:
LA R4,S1
LH R5,LENS1
ICM R5,B'1000',=C' ' padding
MVCL R6,R4 pg=substr(s1,1,lens1)
XPRNT PG,L'PG put skip list(pg)
Line 210:
LA R4,S2
LH R5,LENS2
ICM R5,B'1000',=C' ' padding
MVCL R6,R4 pg=substr(s2,1,lens2)
XPRNT PG,L'PG put skip list(pg)
Line 425:
 
WrapText(Text, LineLength) {
StringReplace, Text, Text, `r`n, %A_Space%, All
while (p := RegExMatch(Text, "(.{1," LineLength "})(\s|\R+|$)", Match, p ? p + StrLen(Match) : 1))
Result .= Match1 ((Match2 = A_Space || Match2 = A_Tab) ? "`n" : Match2)
return, Result
}</lang>
{{Out}}
Line 657:
/* nonsensical hyphens to make greedy wrapping method look bad */
const char *string = "In olden times when wishing still helped one, there lived a king "
"whose daughters were all beautiful, but the youngest was so beautiful "
"that the sun itself, which has seen so much, was astonished whenever "
"it shone-in-her-face. Close-by-the-king's castle lay a great dark "
"forest, and under an old lime-tree in the forest was a well, and when "
"the day was very warm, the king's child went out into the forest and "
"sat down by the side of the cool-fountain, and when she was bored she "
"took a golden ball, and threw it up on high and caught it, and this "
"ball was her favorite plaything.";
 
/* Each but the last of wrapped lines comes with some penalty as the square
of the diff between line length and desired line length. If the line
is longer than desired length, the penalty is multiplied by 100. This
pretty much prohibits the wrapping routine from going over right margin.
If is ok to exceed the margin just a little, something like 20 or 40 will
do.
 
Knuth uses a per-paragraph penalty for line-breaking in TeX, which is--
unlike what I have here--probably bug-free.
*/
 
#define PENALTY_LONG 100
#define PENALTY_SHORT 1
 
typedef struct word_t {
const char *s;
int len;
} *word;
 
word make_word_list(const char *s, int *n)
{
int max_n = 0;
word words = 0;
 
*n = 0;
while (1) {
while (*s && isspace(*s)) s++;
if (!*s) break;
 
if (*n >= max_n) {
if (!(max_n *= 2)) max_n = 2;
words = realloc(words, max_n * sizeof(*words));
}
}
words[*n].s = s;
while (*s && !isspace(*s)) s++;
words[*n].len = s - words[*n].s;
(*n) ++;
}
}
 
return words;
}
 
int greedy_wrap(word words, int count, int cols, int *breaks)
{
int score = 0, line, i, j, d;
 
i = j = line = 0;
while (1) {
if (i == count) {
breaks[j++] = i;
break;
}
}
 
if (!line) {
line = words[i++].len;
continue;
}
}
 
if (line + words[i].len < cols) {
line += words[i++].len + 1;
continue;
}
}
 
breaks[j++] = i;
if (i < count) {
d = cols - line;
if (d > 0) score += PENALTY_SHORT * d * d;
else if (d < 0) score += PENALTY_LONG * d * d;
}
}
 
line = 0;
}
}
breaks[j++] = 0;
 
return score;
}
 
/* tries to make right margin more even; pretty sure there's an off-by-one bug
here somewhere */
int balanced_wrap(word words, int count, int cols, int *breaks)
{
int *best = malloc(sizeof(int) * (count + 1));
 
/* do a greedy wrap to have some baseline score to work with, else
we'll end up with O(2^N) behavior */
int best_score = greedy_wrap(words, count, cols, breaks);
 
void test_wrap(int line_no, int start, int score) {
int line = 0, current_score = -1, d;
 
while (start <= count) {
if (line) line ++;
line += words[start++].len;
d = cols - line;
if (start < count || d < 0) {
if (d > 0)
current_score = score + PENALTY_SHORT * d * d;
else
else
current_score = score + PENALTY_LONG * d * d;
} else {
current_score = score;
}
}
 
if (current_score >= best_score) {
if (d <= 0) return;
continue;
}
}
 
best[line_no] = start;
test_wrap(line_no + 1, start, current_score);
}
}
if (current_score >= 0 && current_score < best_score) {
best_score = current_score;
memcpy(breaks, best, sizeof(int) * (line_no));
}
}
}
}
test_wrap(0, 0, 0);
free(best);
 
return best_score;
}
 
void show_wrap(word list, int count, int *breaks)
{
int i, j;
for (i = j = 0; i < count && breaks[i]; i++) {
while (j < breaks[i]) {
printf("%.*s", list[j].len, list[j].s);
if (j < breaks[i] - 1)
putchar(' ');
j++;
}
}
if (breaks[i]) putchar('\n');
}
}
}
 
int main(void)
{
int len, score, cols;
word list = make_word_list(string, &len);
int *breaks = malloc(sizeof(int) * (len + 1));
 
cols = 80;
score = greedy_wrap(list, len, cols, breaks);
printf("\n== greedy wrap at %d (score %d) ==\n\n", cols, score);
show_wrap(list, len, breaks);
 
score = balanced_wrap(list, len, cols, breaks);
printf("\n== balanced wrap at %d (score %d) ==\n\n", cols, score);
show_wrap(list, len, breaks);
 
 
cols = 32;
score = greedy_wrap(list, len, cols, breaks);
printf("\n== greedy wrap at %d (score %d) ==\n\n", cols, score);
show_wrap(list, len, breaks);
 
score = balanced_wrap(list, len, cols, breaks);
printf("\n== balanced wrap at %d (score %d) ==\n\n", cols, score);
show_wrap(list, len, breaks);
 
return 0;
}</lang>
 
Line 1,403:
 
paragraph( String, Max_line_length ) ->
Lines = lines( string:tokens(String, " "), Max_line_length ),
string:join( Lines, "\n" ).
 
task() ->
Paragraph = "Even today, with proportional fonts and complex layouts, there are still cases where you need to wrap text at a specified column. The basic task is to wrap a paragraph of text in a simple way in your language. If there is a way to do this that is built-in, trivial, or provided in a standard library, show that. Otherwise implement the minimum length greedy algorithm from Wikipedia.",
io:fwrite( "~s~n~n", [paragraph(Paragraph, 72)] ),
io:fwrite( "~s~n~n", [paragraph(Paragraph, 80)] ).
 
 
 
lines( [Word | T], Max_line_length ) ->
{Max_line_length, _Length, Last_line, Lines} = lists:foldl( fun lines_assemble/2, {Max_line_length, erlang:length(Word), Word, []}, T ),
lists:reverse( [Last_line | Lines] ).
 
lines_assemble( Word, {Max, Line_length, Line, Acc} ) when erlang:length(Word) + Line_length > Max -> {Max, erlang:length(Word), Word, [Line | Acc]};
Line 1,603:
Should there be no suitable split in the fragment being appended, then, arbitrarily, if that fragment is short then it is not appended: the line is rolled with trailing spaces. But if it has more than six characters, it will be placed and a crude chop made.
<lang Fortran>
MODULE RIVERRUN !Schemes for re-flowing wads of text to a specified line length.
INTEGER BL,BLIMIT,BM !Fingers for the scratchpad.
PARAMETER (BLIMIT = 222) !This should be enough for normal widths.
CHARACTER*(BLIMIT) BUMF !The scratchpad, accumulating text.
INTEGER OUTBUMF !Output unit number.
DATA OUTBUMF/0/ !Thus detect inadequate initialisation.
PRIVATE BL,BLIMIT,BM !These names are not so unusual
PRIVATE BUMF,OUTBUMF !That no other routine will use them.
CONTAINS
INTEGER FUNCTION LSTNB(TEXT) !Sigh. Last Not Blank.
Concocted yet again by R.N.McLean (whom God preserve) December MM.
Code checking reveals that the Compaq compiler generates a copy of the string and then finds the length of that when using the latter-day intrinsic LEN_TRIM. Madness!
Can't DO WHILE (L.GT.0 .AND. TEXT(L:L).LE.' ') !Control chars. regarded as spaces.
Curse the morons who think it good that the compiler MIGHT evaluate logical expressions fully.
Crude GO TO rather than a DO-loop, because compilers use a loop counter as well as updating the index variable.
Line 1,622:
Checking the indexing of CHARACTER variables for bounds evoked astounding stupidities, such as calculating the length of TEXT(L:L) by subtracting L from L!
Comparison runs of GNASH showed a saving of ~25-30% in its mass data scanning for this, involving all its two-dozen or so single-character comparisons, not just in LSTNB.
CHARACTER*(*),INTENT(IN):: TEXT !The bumf. If there must be copy-in, at least there need not be copy back.
INTEGER L !The length of the bumf.
L = LEN(TEXT) !So, what is it?
1 IF (L.LE.0) GO TO 2 !Are we there yet?
IF (ICHAR(TEXT(L:L)).GT.ICHAR(" ")) GO TO 2 !Control chars are regarded as spaces also.
L = L - 1 !Step back one.
GO TO 1 !And try again.
2 LSTNB = L !The last non-blank, possibly zero.
RETURN !Unsafe to use LSTNB as a variable.
END FUNCTION LSTNB !Compilers can bungle it.
 
SUBROUTINE STARTFLOW(OUT,WIDTH) !Preparation.
INTEGER OUT !Output device.
INTEGER WIDTH !Width limit.
OUTBUMF = OUT !Save these
BM = WIDTH !So that they don't have to be specified every time.
IF (BM.GT.BLIMIT) STOP "Too wide!" !Alas, can't show the values BLIMIT and WIDTH.
BL = 0 !No text already waiting in BUMF
END SUBROUTINE STARTFLOW!Simple enough.
 
SUBROUTINE FLOW(TEXT) !Add to the ongoing BUMF.
CHARACTER*(*) TEXT !The text to append.
INTEGER TL !Its last non-blank.
INTEGER T1,T2 !Fingers to TEXT.
INTEGER L !A length.
IF (OUTBUMF.LT.0) STOP "Call STARTFLOW first!" !Paranoia.
TL = LSTNB(TEXT) !No trailing spaces, please.
IF (TL.LE.0) THEN !A blank (or null) line?
CALL FLUSH !Thus end the paragraph.
RETURN !Perhaps more text will follow, later.
END IF !Curse the (possible) full evaluation of .OR. expressions!
IF (TEXT(1:1).LE." ") CALL FLUSH !This can't be checked above in case LEN(TEXT) = 0.
Chunks of TEXT are to be appended to BUMF.
T1 = 1 !Start at the start, blank or not.
10 IF (BL.GT.0) THEN !If there is text waiting in BUMF,
BL = BL + 1 !Then this latest text is to be appended
BUMF(BL:BL) = " " !After one space.
END IF !So much for the join.
Consider the amount of text to be placed, TEXT(T1:TL)
L = TL - T1 + 1 !Length of text to be placed.
IF (BM - BL .GE. L) THEN !Sufficient space available?
BUMF(BL + 1:BM + L) = TEXT(T1:TL) !Yes. Copy all the remaining text.
BL = BL + L !Advance the finger.
IF (BL .GE. BM - 1) CALL FLUSH !If there is no space for an addendum.
RETURN !Done.
END IF !Otherwise, there is an overhang.
Calculate the available space up to the end of a line. BUMF(BL + 1:BM)
L = BM - BL !The number of characters available in BUMF.
T2 = T1 + L !Finger the first character beyond the take.
IF (TEXT(T2:T2) .LE. " ") GO TO 12 !A splitter character? Happy chance!
T2 = T2 - 1 !Thus the last character of TEXT that could be placed in BUMF.
11 IF (TEXT(T2:T2) .GT. " ") THEN !Are we looking at a space yet?
T2 = T2 - 1 !No. step back one.
IF (T2 .GT. T1) GO TO 11 !And try again, if possible.
IF (L .LE. 6) THEN !No splitter found. For short appendage space,
CALL FLUSH !Starting a new line gives more scope.
GO TO 10 !At the cost of spaces at the end.
END IF !But splitting words is unsavoury too.
T2 = T1 + L - 1 !Alas, no split found.
END IF !So the end-of-line will force a split.
L = T2 - T1 + 1 !The length I settle on.
12 BUMF(BL + 1:BL + L) = TEXT(T1:T1 + L - 1) !I could add a hyphen at the arbitrary chop...
BL = BL + L !The last placed.
CALL FLUSH !The line being full.
Consider what the flushed line didn't take. TEXT(T1 + L:TL)
T1 = T1 + L !Advance to fresh grist.
13 IF (T1.GT.TL) RETURN !Perhaps there is no more. No compound testing, alas.
IF (TEXT(T1:T1).LE." ") THEN !Does a space follow a line split?
T1 = T1 + 1 !Yes. It would appear as a leading space in the output.
GO TO 13 !But the line split stands in for all that.
END IF !So, speed past all such.
IF (T1.LE.TL) GO TO 10!Does anything remain?
RETURN !Nope.
CONTAINS !A convenience.
SUBROUTINE FLUSH !Save on repetition.
IF (BL.GT.0) WRITE (OUTBUMF,"(A)") BUMF(1:BL) !Roll the bumf, if any.
BL = 0 !And be ready for more.
END SUBROUTINE FLUSH !Thus avoid the verbosity of repeated begin ... end blocks.
END SUBROUTINE FLOW !Invoke with one large blob, or, pieces.
END MODULE RIVERRUN !Flush the tail end with a null text.
 
PROGRAM TEST
Line 1,720:
1 READ (IN,2) BUMF
2 FORMAT (A)
IF (BUMF(1:1).NE."C") GO TO 1 !No comment block yet.
CALL STARTFLOW(MSG,66) !Found it!
3 CALL FLOW(BUMF) !Roll its text.
READ (IN,2) BUMF !Grab another line.
IF (BUMF(1:1).EQ."C") GO TO 3 !And if a comment, append.
CALL FLOW("")
CLOSE (IN)
Line 2,110:
public class WordWrap
{
int defaultLineWidth=80;
int defaultSpaceWidth=1;
void minNumLinesWrap(String text)
{
{
minNumLinesWrap(text,defaultLineWidth);
}
}
void minNumLinesWrap(String text,int LineWidth)
{
{
StringTokenizer st=new StringTokenizer(text);
int SpaceLeft=LineWidth;
int SpaceWidth=defaultSpaceWidth;
while(st.hasMoreTokens())
{
{
String word=st.nextToken();
if((word.length()+SpaceWidth)>SpaceLeft)
{
{
System.out.print("\n"+word+" ");
SpaceLeft=LineWidth-word.length();
}
}
else
{
{
System.out.print(word+" ");
SpaceLeft-=(word.length()+SpaceWidth);
}
}
}
}
}
}
public static void main(String[] args)
{
{
WordWrap now=new WordWrap();
String wodehouse="Old Mr MacFarland (_said Henry_) started the place fifteen years ago. He was a widower with one son and what you might call half a daughter. That's to say, he had adopted her. Katie was her name, and she was the child of a dead friend of his. The son's name was Andy. A little freckled nipper he was when I first knew him--one of those silent kids that don't say much and have as much obstinacy in them as if they were mules. Many's the time, in them days, I've clumped him on the head and told him to do something; and he didn't run yelling to his pa, same as most kids would have done, but just said nothing and went on not doing whatever it was I had told him to do. That was the sort of disposition Andy had, and it grew on him. Why, when he came back from Oxford College the time the old man sent for him--what I'm going to tell you about soon--he had a jaw on him like the ram of a battleship. Katie was the kid for my money. I liked Katie. We all liked Katie.";
System.out.println("DEFAULT:");
now.minNumLinesWrap(wodehouse);
System.out.println("\n\nLINEWIDTH=120");
now.minNumLinesWrap(wodehouse,120);
}
}
 
}
Line 2,610:
=={{header|Lasso}}==
<lang Lasso>define wordwrap(
text::string,
row_length::integer = 75
) => {
return regexp(`(?is)(.{1,` + #row_length + `})(?:$|\W)+`, '$1<br />\n', #text, true) -> replaceall
}
 
Line 3,617:
 
foreach($word in $divide){
if($word.length+1 -gt $spaceleft){
$output+="`n$word "
$spaceleft=$width-($word.length+1)
} else {
$output+="$word "
$spaceleft-=$word.length+1
}
}
}
 
Line 3,699:
foreach ($word in $words)
{
if($word.Length + 1 -gt $remaining)
{
$output += "`n$word "
$remaining = $Width - ($word.Length + 1)
}
else
{
$output += "$word "
$remaining -= $word.Length + 1
}
}
 
Line 3,851:
DataSection
Data.s "In olden times when wishing still helped one, there lived a king "+
"whose daughters were all beautiful, but the youngest was so beautiful "+
"that the sun itself, which has seen so much, was astonished whenever "+
"it shone-in-her-face. Close-by-the-king's castle lay a great dark "+
"forest, and under an old lime-tree in the forest was a well, and when "+
"the day was very warm, the king's child went out into the forest and "+
"sat down by the side of the cool-fountain, and when she was bored she "+
"took a golden ball, and threw it up on high and caught it, and this "+
"ball was her favorite plaything."
EndDataSection
 
Line 4,627:
whose daughters were all beautiful, but the youngest was so | one, there lived a king whose daughters
beautiful that the sun itself, which has seen so much, was | were all beautiful, but the youngest was
astonished whenever it shone in her face. | so beautiful that the sun itself, which
| has seen so much, was astonished whenever
| it shone in her face.
</pre>
Without Browser
Line 4,655:
wend
print docOut$</lang>
 
=={{header|Rust}}==
This is an implementation of the simple greedy algorithm.
 
<lang Rust>#[derive(Clone, Debug)]
pub struct LineComposer<I> {
words: I,
width: usize,
current: Option<String>,
}
 
impl<I> LineComposer<I> {
pub(crate) fn new<S>(words: I, width: usize) -> Self
where
I: Iterator<Item = S>,
S: AsRef<str>,
{
LineComposer {
words,
width,
current: None,
}
}
}
 
impl<I, S> Iterator for LineComposer<I>
where
I: Iterator<Item = S>,
S: AsRef<str>,
{
type Item = String;
 
fn next(&mut self) -> Option<Self::Item> {
let mut next = match self.words.next() {
None => return self.current.take(),
Some(value) => value,
};
 
let mut current = self.current.take().unwrap_or_else(String::new);
 
loop {
let word = next.as_ref();
if self.width <= current.len() + word.len() {
self.current = Some(String::from(word));
// If the first word itself is too long, avoid producing an
// empty line. Continue instead with the next word.
if !current.is_empty() {
return Some(current);
}
}
 
if !current.is_empty() {
current.push_str(" ")
}
 
current.push_str(word);
 
match self.words.next() {
None => return Some(current), // Last line, current remains None
Some(word) => next = word,
}
}
}
}
 
// This part is just to extend all suitable iterators with LineComposer
 
pub trait ComposeLines: Iterator {
fn compose_lines(self, width: usize) -> LineComposer<Self>
where
Self: Sized,
Self::Item: AsRef<str>,
{
LineComposer::new(self, width)
}
}
 
impl<T, S> ComposeLines for T
where
T: Iterator<Item = S>,
S: AsRef<str>,
{
}
 
fn main() {
let text = r"
In olden times when wishing still helped one, there lived a king whose
daughters were all beautiful, but the youngest was so beautiful that the
sun itself, which has seen so much, was astonished whenever it shone in
her face. Close by the king's castle lay a great dark forest, and under
an old lime tree in the forest was a well, and when the day was very
warm, the king's child went out into the forest and sat down by the side
of the cool fountain, and when she was bored she took a golden ball, and
threw it up on high and caught it, and this ball was her favorite
plaything.";
 
text.split_whitespace()
.compose_lines(80)
.for_each(|line| println!("{}", line));
}
</lang>
 
{{out}}
<pre>
In olden times when wishing still helped one, there lived a king whose daughters
were all beautiful, but the youngest was so beautiful that the sun itself, which
has seen so much, was astonished whenever it shone in her face. Close by the
king's castle lay a great dark forest, and under an old lime tree in the forest
was a well, and when the day was very warm, the king's child went out into the
forest and sat down by the side of the cool fountain, and when she was bored she
took a golden ball, and threw it up on high and caught it, and this ball was her
favorite plaything.
</pre>
 
=={{header|Scala}}==
Line 4,991 ⟶ 5,104:
}
}
 
var sww = SmartWordWrap();
 
var words = %w(aaa bb cc ddddd);
var wrapped = sww.wrap(words, 6);
 
say wrapped;</lang>
{{out}}
Line 5,013 ⟶ 5,126:
set RE "^(.{1,$n})(?:\\s+(.*))?$"
for {set result ""} {[regexp $RE $text -> line text]} {} {
append result $line "\n"
}
return [string trimright $result "\n"]
Line 5,107 ⟶ 5,220:
column = 60
text = "In olden times when wishing still helped one, there lived a king " &_
"whose daughters were all beautiful, but the youngest was so beautiful "&_
"that the sun itself, which has seen so much, was astonished whenever "&_
"it shone-in-her-face. Close-by-the-king's castle lay a great dark "&_
"forest, and under an old lime-tree in the forest was a well, and when "&_
"the day was very warm, the king's child went out into the forest and "&_
"sat down by the side of the cool-fountain, and when she was bored she "&_
"took a golden ball, and threw it up on high and caught it, and this "&_
"ball was her favorite plaything."
 
Call wordwrap(text,column)
 
Sub wordwrap(s,n)
word = Split(s," ")
row = ""
For i = 0 To UBound(word)
If Len(row) = 0 Then
row = row & word(i)
ElseIf Len(row & " " & word(i)) <= n Then
row = row & " " & word(i)
Else
WScript.StdOut.WriteLine row
row = word(i)
End If
Next
If Len(row) > 0 Then
WScript.StdOut.WriteLine row
End If
End Sub
</lang>
Line 5,180 ⟶ 5,293:
 
sub words(w$, p$(), d$)
local n, i, p
n = split(w$, p$(), d$)
p = 1
for i = 1 to n
p$(i) = p$(i) + mid$(w$, p + len(p$(i)), 1)
p = p + len(p$(i))
next i
return n
end sub</lang>
 
Line 5,214 ⟶ 5,327:
length=72, calcIndents=True){
sink:=Data();
getIndents:='wrap(w){ // look at first two lines to indent paragraph
reg lines=L(), len=0, prefix="", one=True;
do(2){
if(w._next()){
lines.append(line:=w.value);
word:=line.split(Void,1)[0,1]; // get first word, if line !blank
if(word){
p:=line[0,line.find(word[0]]);
if(one){ sink.write(p); len=p.len(); one=False; }
else prefix=p;
}
}
}
w.push(lines.xplode()); // put first two lines back to be formated
Line 5,231 ⟶ 5,344:
};
 
reg len=0, prefix="", w=text.walker(1); // lines
if(calcIndents) len,prefix=getIndents(w);
foreach line in (w){
if(not line.strip()){ // blank line
sink.write("\n",line); // blank line redux
if(calcIndents) len,prefix=getIndents(w);
else len=0; // restart formating
}else
len=line.split().reduce('wrap(len,word){
n:=word.len();
if(len==0) { sink.write(word); return(n); }
nn:=n+1+len; if(nn<=length) { sink.write(" ",word); return(nn); }
sink.write("\n",prefix,word); return(prefix.len()+word.len());
},len);
}
sink
Anonymous user
Cookies help us deliver our services. By using our services, you agree to our use of cookies.