Word wrap: Difference between revisions

Content added Content deleted
(Add source for Rust)
Line 189: Line 189:
LA R4,S1
LA R4,S1
LH R5,LENS1
LH R5,LENS1
ICM R5,B'1000',=C' ' padding
ICM R5,B'1000',=C' ' padding
MVCL R6,R4 pg=substr(s1,1,lens1)
MVCL R6,R4 pg=substr(s1,1,lens1)
XPRNT PG,L'PG put skip list(pg)
XPRNT PG,L'PG put skip list(pg)
Line 210: Line 210:
LA R4,S2
LA R4,S2
LH R5,LENS2
LH R5,LENS2
ICM R5,B'1000',=C' ' padding
ICM R5,B'1000',=C' ' padding
MVCL R6,R4 pg=substr(s2,1,lens2)
MVCL R6,R4 pg=substr(s2,1,lens2)
XPRNT PG,L'PG put skip list(pg)
XPRNT PG,L'PG put skip list(pg)
Line 425: Line 425:


WrapText(Text, LineLength) {
WrapText(Text, LineLength) {
StringReplace, Text, Text, `r`n, %A_Space%, All
StringReplace, Text, Text, `r`n, %A_Space%, All
while (p := RegExMatch(Text, "(.{1," LineLength "})(\s|\R+|$)", Match, p ? p + StrLen(Match) : 1))
while (p := RegExMatch(Text, "(.{1," LineLength "})(\s|\R+|$)", Match, p ? p + StrLen(Match) : 1))
Result .= Match1 ((Match2 = A_Space || Match2 = A_Tab) ? "`n" : Match2)
Result .= Match1 ((Match2 = A_Space || Match2 = A_Tab) ? "`n" : Match2)
return, Result
return, Result
}</lang>
}</lang>
{{Out}}
{{Out}}
Line 657: Line 657:
/* nonsensical hyphens to make greedy wrapping method look bad */
/* nonsensical hyphens to make greedy wrapping method look bad */
const char *string = "In olden times when wishing still helped one, there lived a king "
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 "
"whose daughters were all beautiful, but the youngest was so beautiful "
"that the sun itself, which has seen so much, was astonished whenever "
"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 "
"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 "
"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 "
"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 "
"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 "
"took a golden ball, and threw it up on high and caught it, and this "
"ball was her favorite plaything.";
"ball was her favorite plaything.";


/* Each but the last of wrapped lines comes with some penalty as the square
/* 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
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
is longer than desired length, the penalty is multiplied by 100. This
pretty much prohibits the wrapping routine from going over right margin.
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
If is ok to exceed the margin just a little, something like 20 or 40 will
do.
do.


Knuth uses a per-paragraph penalty for line-breaking in TeX, which is--
Knuth uses a per-paragraph penalty for line-breaking in TeX, which is--
unlike what I have here--probably bug-free.
unlike what I have here--probably bug-free.
*/
*/


#define PENALTY_LONG 100
#define PENALTY_LONG 100
#define PENALTY_SHORT 1
#define PENALTY_SHORT 1


typedef struct word_t {
typedef struct word_t {
const char *s;
const char *s;
int len;
int len;
} *word;
} *word;


word make_word_list(const char *s, int *n)
word make_word_list(const char *s, int *n)
{
{
int max_n = 0;
int max_n = 0;
word words = 0;
word words = 0;


*n = 0;
*n = 0;
while (1) {
while (1) {
while (*s && isspace(*s)) s++;
while (*s && isspace(*s)) s++;
if (!*s) break;
if (!*s) break;


if (*n >= max_n) {
if (*n >= max_n) {
if (!(max_n *= 2)) max_n = 2;
if (!(max_n *= 2)) max_n = 2;
words = realloc(words, max_n * sizeof(*words));
words = realloc(words, max_n * sizeof(*words));
}
}
words[*n].s = s;
words[*n].s = s;
while (*s && !isspace(*s)) s++;
while (*s && !isspace(*s)) s++;
words[*n].len = s - words[*n].s;
words[*n].len = s - words[*n].s;
(*n) ++;
(*n) ++;
}
}


return words;
return words;
}
}


int greedy_wrap(word words, int count, int cols, int *breaks)
int greedy_wrap(word words, int count, int cols, int *breaks)
{
{
int score = 0, line, i, j, d;
int score = 0, line, i, j, d;


i = j = line = 0;
i = j = line = 0;
while (1) {
while (1) {
if (i == count) {
if (i == count) {
breaks[j++] = i;
breaks[j++] = i;
break;
break;
}
}


if (!line) {
if (!line) {
line = words[i++].len;
line = words[i++].len;
continue;
continue;
}
}


if (line + words[i].len < cols) {
if (line + words[i].len < cols) {
line += words[i++].len + 1;
line += words[i++].len + 1;
continue;
continue;
}
}


breaks[j++] = i;
breaks[j++] = i;
if (i < count) {
if (i < count) {
d = cols - line;
d = cols - line;
if (d > 0) score += PENALTY_SHORT * d * d;
if (d > 0) score += PENALTY_SHORT * d * d;
else if (d < 0) score += PENALTY_LONG * d * d;
else if (d < 0) score += PENALTY_LONG * d * d;
}
}


line = 0;
line = 0;
}
}
breaks[j++] = 0;
breaks[j++] = 0;


return score;
return score;
}
}


/* tries to make right margin more even; pretty sure there's an off-by-one bug
/* tries to make right margin more even; pretty sure there's an off-by-one bug
here somewhere */
here somewhere */
int balanced_wrap(word words, int count, int cols, int *breaks)
int balanced_wrap(word words, int count, int cols, int *breaks)
{
{
int *best = malloc(sizeof(int) * (count + 1));
int *best = malloc(sizeof(int) * (count + 1));


/* do a greedy wrap to have some baseline score to work with, else
/* do a greedy wrap to have some baseline score to work with, else
we'll end up with O(2^N) behavior */
we'll end up with O(2^N) behavior */
int best_score = greedy_wrap(words, count, cols, breaks);
int best_score = greedy_wrap(words, count, cols, breaks);


void test_wrap(int line_no, int start, int score) {
void test_wrap(int line_no, int start, int score) {
int line = 0, current_score = -1, d;
int line = 0, current_score = -1, d;


while (start <= count) {
while (start <= count) {
if (line) line ++;
if (line) line ++;
line += words[start++].len;
line += words[start++].len;
d = cols - line;
d = cols - line;
if (start < count || d < 0) {
if (start < count || d < 0) {
if (d > 0)
if (d > 0)
current_score = score + PENALTY_SHORT * d * d;
current_score = score + PENALTY_SHORT * d * d;
else
else
current_score = score + PENALTY_LONG * d * d;
current_score = score + PENALTY_LONG * d * d;
} else {
} else {
current_score = score;
current_score = score;
}
}


if (current_score >= best_score) {
if (current_score >= best_score) {
if (d <= 0) return;
if (d <= 0) return;
continue;
continue;
}
}


best[line_no] = start;
best[line_no] = start;
test_wrap(line_no + 1, start, current_score);
test_wrap(line_no + 1, start, current_score);
}
}
if (current_score >= 0 && current_score < best_score) {
if (current_score >= 0 && current_score < best_score) {
best_score = current_score;
best_score = current_score;
memcpy(breaks, best, sizeof(int) * (line_no));
memcpy(breaks, best, sizeof(int) * (line_no));
}
}
}
}
test_wrap(0, 0, 0);
test_wrap(0, 0, 0);
free(best);
free(best);


return best_score;
return best_score;
}
}


void show_wrap(word list, int count, int *breaks)
void show_wrap(word list, int count, int *breaks)
{
{
int i, j;
int i, j;
for (i = j = 0; i < count && breaks[i]; i++) {
for (i = j = 0; i < count && breaks[i]; i++) {
while (j < breaks[i]) {
while (j < breaks[i]) {
printf("%.*s", list[j].len, list[j].s);
printf("%.*s", list[j].len, list[j].s);
if (j < breaks[i] - 1)
if (j < breaks[i] - 1)
putchar(' ');
putchar(' ');
j++;
j++;
}
}
if (breaks[i]) putchar('\n');
if (breaks[i]) putchar('\n');
}
}
}
}


int main(void)
int main(void)
{
{
int len, score, cols;
int len, score, cols;
word list = make_word_list(string, &len);
word list = make_word_list(string, &len);
int *breaks = malloc(sizeof(int) * (len + 1));
int *breaks = malloc(sizeof(int) * (len + 1));


cols = 80;
cols = 80;
score = greedy_wrap(list, len, cols, breaks);
score = greedy_wrap(list, len, cols, breaks);
printf("\n== greedy wrap at %d (score %d) ==\n\n", cols, score);
printf("\n== greedy wrap at %d (score %d) ==\n\n", cols, score);
show_wrap(list, len, breaks);
show_wrap(list, len, breaks);


score = balanced_wrap(list, len, cols, breaks);
score = balanced_wrap(list, len, cols, breaks);
printf("\n== balanced wrap at %d (score %d) ==\n\n", cols, score);
printf("\n== balanced wrap at %d (score %d) ==\n\n", cols, score);
show_wrap(list, len, breaks);
show_wrap(list, len, breaks);




cols = 32;
cols = 32;
score = greedy_wrap(list, len, cols, breaks);
score = greedy_wrap(list, len, cols, breaks);
printf("\n== greedy wrap at %d (score %d) ==\n\n", cols, score);
printf("\n== greedy wrap at %d (score %d) ==\n\n", cols, score);
show_wrap(list, len, breaks);
show_wrap(list, len, breaks);


score = balanced_wrap(list, len, cols, breaks);
score = balanced_wrap(list, len, cols, breaks);
printf("\n== balanced wrap at %d (score %d) ==\n\n", cols, score);
printf("\n== balanced wrap at %d (score %d) ==\n\n", cols, score);
show_wrap(list, len, breaks);
show_wrap(list, len, breaks);


return 0;
return 0;
}</lang>
}</lang>


Line 1,403: Line 1,403:


paragraph( String, Max_line_length ) ->
paragraph( String, Max_line_length ) ->
Lines = lines( string:tokens(String, " "), Max_line_length ),
Lines = lines( string:tokens(String, " "), Max_line_length ),
string:join( Lines, "\n" ).
string:join( Lines, "\n" ).


task() ->
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.",
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, 72)] ),
io:fwrite( "~s~n~n", [paragraph(Paragraph, 80)] ).
io:fwrite( "~s~n~n", [paragraph(Paragraph, 80)] ).






lines( [Word | T], Max_line_length ) ->
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 ),
{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] ).
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]};
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: 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.
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>
<lang Fortran>
MODULE RIVERRUN !Schemes for re-flowing wads of text to a specified line length.
MODULE RIVERRUN !Schemes for re-flowing wads of text to a specified line length.
INTEGER BL,BLIMIT,BM !Fingers for the scratchpad.
INTEGER BL,BLIMIT,BM !Fingers for the scratchpad.
PARAMETER (BLIMIT = 222) !This should be enough for normal widths.
PARAMETER (BLIMIT = 222) !This should be enough for normal widths.
CHARACTER*(BLIMIT) BUMF !The scratchpad, accumulating text.
CHARACTER*(BLIMIT) BUMF !The scratchpad, accumulating text.
INTEGER OUTBUMF !Output unit number.
INTEGER OUTBUMF !Output unit number.
DATA OUTBUMF/0/ !Thus detect inadequate initialisation.
DATA OUTBUMF/0/ !Thus detect inadequate initialisation.
PRIVATE BL,BLIMIT,BM !These names are not so unusual
PRIVATE BL,BLIMIT,BM !These names are not so unusual
PRIVATE BUMF,OUTBUMF !That no other routine will use them.
PRIVATE BUMF,OUTBUMF !That no other routine will use them.
CONTAINS
CONTAINS
INTEGER FUNCTION LSTNB(TEXT) !Sigh. Last Not Blank.
INTEGER FUNCTION LSTNB(TEXT) !Sigh. Last Not Blank.
Concocted yet again by R.N.McLean (whom God preserve) December MM.
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!
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.
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.
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.
Crude GO TO rather than a DO-loop, because compilers use a loop counter as well as updating the index variable.
Line 1,622: 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!
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.
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.
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.
INTEGER L !The length of the bumf.
L = LEN(TEXT) !So, what is it?
L = LEN(TEXT) !So, what is it?
1 IF (L.LE.0) GO TO 2 !Are we there yet?
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.
IF (ICHAR(TEXT(L:L)).GT.ICHAR(" ")) GO TO 2 !Control chars are regarded as spaces also.
L = L - 1 !Step back one.
L = L - 1 !Step back one.
GO TO 1 !And try again.
GO TO 1 !And try again.
2 LSTNB = L !The last non-blank, possibly zero.
2 LSTNB = L !The last non-blank, possibly zero.
RETURN !Unsafe to use LSTNB as a variable.
RETURN !Unsafe to use LSTNB as a variable.
END FUNCTION LSTNB !Compilers can bungle it.
END FUNCTION LSTNB !Compilers can bungle it.


SUBROUTINE STARTFLOW(OUT,WIDTH) !Preparation.
SUBROUTINE STARTFLOW(OUT,WIDTH) !Preparation.
INTEGER OUT !Output device.
INTEGER OUT !Output device.
INTEGER WIDTH !Width limit.
INTEGER WIDTH !Width limit.
OUTBUMF = OUT !Save these
OUTBUMF = OUT !Save these
BM = WIDTH !So that they don't have to be specified every time.
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.
IF (BM.GT.BLIMIT) STOP "Too wide!" !Alas, can't show the values BLIMIT and WIDTH.
BL = 0 !No text already waiting in BUMF
BL = 0 !No text already waiting in BUMF
END SUBROUTINE STARTFLOW!Simple enough.
END SUBROUTINE STARTFLOW!Simple enough.


SUBROUTINE FLOW(TEXT) !Add to the ongoing BUMF.
SUBROUTINE FLOW(TEXT) !Add to the ongoing BUMF.
CHARACTER*(*) TEXT !The text to append.
CHARACTER*(*) TEXT !The text to append.
INTEGER TL !Its last non-blank.
INTEGER TL !Its last non-blank.
INTEGER T1,T2 !Fingers to TEXT.
INTEGER T1,T2 !Fingers to TEXT.
INTEGER L !A length.
INTEGER L !A length.
IF (OUTBUMF.LT.0) STOP "Call STARTFLOW first!" !Paranoia.
IF (OUTBUMF.LT.0) STOP "Call STARTFLOW first!" !Paranoia.
TL = LSTNB(TEXT) !No trailing spaces, please.
TL = LSTNB(TEXT) !No trailing spaces, please.
IF (TL.LE.0) THEN !A blank (or null) line?
IF (TL.LE.0) THEN !A blank (or null) line?
CALL FLUSH !Thus end the paragraph.
CALL FLUSH !Thus end the paragraph.
RETURN !Perhaps more text will follow, later.
RETURN !Perhaps more text will follow, later.
END IF !Curse the (possible) full evaluation of .OR. expressions!
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.
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.
Chunks of TEXT are to be appended to BUMF.
T1 = 1 !Start at the start, blank or not.
T1 = 1 !Start at the start, blank or not.
10 IF (BL.GT.0) THEN !If there is text waiting in BUMF,
10 IF (BL.GT.0) THEN !If there is text waiting in BUMF,
BL = BL + 1 !Then this latest text is to be appended
BL = BL + 1 !Then this latest text is to be appended
BUMF(BL:BL) = " " !After one space.
BUMF(BL:BL) = " " !After one space.
END IF !So much for the join.
END IF !So much for the join.
Consider the amount of text to be placed, TEXT(T1:TL)
Consider the amount of text to be placed, TEXT(T1:TL)
L = TL - T1 + 1 !Length of text to be placed.
L = TL - T1 + 1 !Length of text to be placed.
IF (BM - BL .GE. L) THEN !Sufficient space available?
IF (BM - BL .GE. L) THEN !Sufficient space available?
BUMF(BL + 1:BM + L) = TEXT(T1:TL) !Yes. Copy all the remaining text.
BUMF(BL + 1:BM + L) = TEXT(T1:TL) !Yes. Copy all the remaining text.
BL = BL + L !Advance the finger.
BL = BL + L !Advance the finger.
IF (BL .GE. BM - 1) CALL FLUSH !If there is no space for an addendum.
IF (BL .GE. BM - 1) CALL FLUSH !If there is no space for an addendum.
RETURN !Done.
RETURN !Done.
END IF !Otherwise, there is an overhang.
END IF !Otherwise, there is an overhang.
Calculate the available space up to the end of a line. BUMF(BL + 1:BM)
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.
L = BM - BL !The number of characters available in BUMF.
T2 = T1 + L !Finger the first character beyond the take.
T2 = T1 + L !Finger the first character beyond the take.
IF (TEXT(T2:T2) .LE. " ") GO TO 12 !A splitter character? Happy chance!
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.
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?
11 IF (TEXT(T2:T2) .GT. " ") THEN !Are we looking at a space yet?
T2 = T2 - 1 !No. step back one.
T2 = T2 - 1 !No. step back one.
IF (T2 .GT. T1) GO TO 11 !And try again, if possible.
IF (T2 .GT. T1) GO TO 11 !And try again, if possible.
IF (L .LE. 6) THEN !No splitter found. For short appendage space,
IF (L .LE. 6) THEN !No splitter found. For short appendage space,
CALL FLUSH !Starting a new line gives more scope.
CALL FLUSH !Starting a new line gives more scope.
GO TO 10 !At the cost of spaces at the end.
GO TO 10 !At the cost of spaces at the end.
END IF !But splitting words is unsavoury too.
END IF !But splitting words is unsavoury too.
T2 = T1 + L - 1 !Alas, no split found.
T2 = T1 + L - 1 !Alas, no split found.
END IF !So the end-of-line will force a split.
END IF !So the end-of-line will force a split.
L = T2 - T1 + 1 !The length I settle on.
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...
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.
BL = BL + L !The last placed.
CALL FLUSH !The line being full.
CALL FLUSH !The line being full.
Consider what the flushed line didn't take. TEXT(T1 + L:TL)
Consider what the flushed line didn't take. TEXT(T1 + L:TL)
T1 = T1 + L !Advance to fresh grist.
T1 = T1 + L !Advance to fresh grist.
13 IF (T1.GT.TL) RETURN !Perhaps there is no more. No compound testing, alas.
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?
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.
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.
GO TO 13 !But the line split stands in for all that.
END IF !So, speed past all such.
END IF !So, speed past all such.
IF (T1.LE.TL) GO TO 10!Does anything remain?
IF (T1.LE.TL) GO TO 10!Does anything remain?
RETURN !Nope.
RETURN !Nope.
CONTAINS !A convenience.
CONTAINS !A convenience.
SUBROUTINE FLUSH !Save on repetition.
SUBROUTINE FLUSH !Save on repetition.
IF (BL.GT.0) WRITE (OUTBUMF,"(A)") BUMF(1:BL) !Roll the bumf, if any.
IF (BL.GT.0) WRITE (OUTBUMF,"(A)") BUMF(1:BL) !Roll the bumf, if any.
BL = 0 !And be ready for more.
BL = 0 !And be ready for more.
END SUBROUTINE FLUSH !Thus avoid the verbosity of repeated begin ... end blocks.
END SUBROUTINE FLUSH !Thus avoid the verbosity of repeated begin ... end blocks.
END SUBROUTINE FLOW !Invoke with one large blob, or, pieces.
END SUBROUTINE FLOW !Invoke with one large blob, or, pieces.
END MODULE RIVERRUN !Flush the tail end with a null text.
END MODULE RIVERRUN !Flush the tail end with a null text.


PROGRAM TEST
PROGRAM TEST
Line 1,720: Line 1,720:
1 READ (IN,2) BUMF
1 READ (IN,2) BUMF
2 FORMAT (A)
2 FORMAT (A)
IF (BUMF(1:1).NE."C") GO TO 1 !No comment block yet.
IF (BUMF(1:1).NE."C") GO TO 1 !No comment block yet.
CALL STARTFLOW(MSG,66) !Found it!
CALL STARTFLOW(MSG,66) !Found it!
3 CALL FLOW(BUMF) !Roll its text.
3 CALL FLOW(BUMF) !Roll its text.
READ (IN,2) BUMF !Grab another line.
READ (IN,2) BUMF !Grab another line.
IF (BUMF(1:1).EQ."C") GO TO 3 !And if a comment, append.
IF (BUMF(1:1).EQ."C") GO TO 3 !And if a comment, append.
CALL FLOW("")
CALL FLOW("")
CLOSE (IN)
CLOSE (IN)
Line 2,110: Line 2,110:
public class WordWrap
public class WordWrap
{
{
int defaultLineWidth=80;
int defaultLineWidth=80;
int defaultSpaceWidth=1;
int defaultSpaceWidth=1;
void minNumLinesWrap(String text)
void minNumLinesWrap(String text)
{
{
minNumLinesWrap(text,defaultLineWidth);
minNumLinesWrap(text,defaultLineWidth);
}
}
void minNumLinesWrap(String text,int LineWidth)
void minNumLinesWrap(String text,int LineWidth)
{
{
StringTokenizer st=new StringTokenizer(text);
StringTokenizer st=new StringTokenizer(text);
int SpaceLeft=LineWidth;
int SpaceLeft=LineWidth;
int SpaceWidth=defaultSpaceWidth;
int SpaceWidth=defaultSpaceWidth;
while(st.hasMoreTokens())
while(st.hasMoreTokens())
{
{
String word=st.nextToken();
String word=st.nextToken();
if((word.length()+SpaceWidth)>SpaceLeft)
if((word.length()+SpaceWidth)>SpaceLeft)
{
{
System.out.print("\n"+word+" ");
System.out.print("\n"+word+" ");
SpaceLeft=LineWidth-word.length();
SpaceLeft=LineWidth-word.length();
}
}
else
else
{
{
System.out.print(word+" ");
System.out.print(word+" ");
SpaceLeft-=(word.length()+SpaceWidth);
SpaceLeft-=(word.length()+SpaceWidth);
}
}
}
}
}
}
public static void main(String[] args)
public static void main(String[] args)
{
{
WordWrap now=new WordWrap();
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.";
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:");
System.out.println("DEFAULT:");
now.minNumLinesWrap(wodehouse);
now.minNumLinesWrap(wodehouse);
System.out.println("\n\nLINEWIDTH=120");
System.out.println("\n\nLINEWIDTH=120");
now.minNumLinesWrap(wodehouse,120);
now.minNumLinesWrap(wodehouse,120);
}
}


}
}
Line 2,610: Line 2,610:
=={{header|Lasso}}==
=={{header|Lasso}}==
<lang Lasso>define wordwrap(
<lang Lasso>define wordwrap(
text::string,
text::string,
row_length::integer = 75
row_length::integer = 75
) => {
) => {
return regexp(`(?is)(.{1,` + #row_length + `})(?:$|\W)+`, '$1<br />\n', #text, true) -> replaceall
return regexp(`(?is)(.{1,` + #row_length + `})(?:$|\W)+`, '$1<br />\n', #text, true) -> replaceall
}
}


Line 3,617: Line 3,617:


foreach($word in $divide){
foreach($word in $divide){
if($word.length+1 -gt $spaceleft){
if($word.length+1 -gt $spaceleft){
$output+="`n$word "
$output+="`n$word "
$spaceleft=$width-($word.length+1)
$spaceleft=$width-($word.length+1)
} else {
} else {
$output+="$word "
$output+="$word "
$spaceleft-=$word.length+1
$spaceleft-=$word.length+1
}
}
}
}


Line 3,699: Line 3,699:
foreach ($word in $words)
foreach ($word in $words)
{
{
if($word.Length + 1 -gt $remaining)
if($word.Length + 1 -gt $remaining)
{
{
$output += "`n$word "
$output += "`n$word "
$remaining = $Width - ($word.Length + 1)
$remaining = $Width - ($word.Length + 1)
}
}
else
else
{
{
$output += "$word "
$output += "$word "
$remaining -= $word.Length + 1
$remaining -= $word.Length + 1
}
}
}
}


Line 3,851: Line 3,851:
DataSection
DataSection
Data.s "In olden times when wishing still helped one, there lived a king "+
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 "+
"whose daughters were all beautiful, but the youngest was so beautiful "+
"that the sun itself, which has seen so much, was astonished whenever "+
"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 "+
"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 "+
"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 "+
"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 "+
"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 "+
"took a golden ball, and threw it up on high and caught it, and this "+
"ball was her favorite plaything."
"ball was her favorite plaything."
EndDataSection
EndDataSection


Line 4,627: Line 4,627:
whose daughters were all beautiful, but the youngest was so | one, there lived a king whose daughters
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
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
astonished whenever it shone in her face. | so beautiful that the sun itself, which
| has seen so much, was astonished whenever
| has seen so much, was astonished whenever
| it shone in her face.
| it shone in her face.
</pre>
</pre>
Without Browser
Without Browser
Line 4,655: Line 4,655:
wend
wend
print docOut$</lang>
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}}==
=={{header|Scala}}==
Line 4,991: Line 5,104:
}
}
}
}
 
var sww = SmartWordWrap();
var sww = SmartWordWrap();
 
var words = %w(aaa bb cc ddddd);
var words = %w(aaa bb cc ddddd);
var wrapped = sww.wrap(words, 6);
var wrapped = sww.wrap(words, 6);
 
say wrapped;</lang>
say wrapped;</lang>
{{out}}
{{out}}
Line 5,013: Line 5,126:
set RE "^(.{1,$n})(?:\\s+(.*))?$"
set RE "^(.{1,$n})(?:\\s+(.*))?$"
for {set result ""} {[regexp $RE $text -> line text]} {} {
for {set result ""} {[regexp $RE $text -> line text]} {} {
append result $line "\n"
append result $line "\n"
}
}
return [string trimright $result "\n"]
return [string trimright $result "\n"]
Line 5,107: Line 5,220:
column = 60
column = 60
text = "In olden times when wishing still helped one, there lived a king " &_
text = "In olden times when wishing still helped one, there lived a king " &_
"whose daughters were all beautiful, but the youngest was so beautiful "&_
"whose daughters were all beautiful, but the youngest was so beautiful "&_
"that the sun itself, which has seen so much, was astonished whenever "&_
"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 "&_
"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 "&_
"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 "&_
"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 "&_
"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 "&_
"took a golden ball, and threw it up on high and caught it, and this "&_
"ball was her favorite plaything."
"ball was her favorite plaything."


Call wordwrap(text,column)
Call wordwrap(text,column)


Sub wordwrap(s,n)
Sub wordwrap(s,n)
word = Split(s," ")
word = Split(s," ")
row = ""
row = ""
For i = 0 To UBound(word)
For i = 0 To UBound(word)
If Len(row) = 0 Then
If Len(row) = 0 Then
row = row & word(i)
row = row & word(i)
ElseIf Len(row & " " & word(i)) <= n Then
ElseIf Len(row & " " & word(i)) <= n Then
row = row & " " & word(i)
row = row & " " & word(i)
Else
Else
WScript.StdOut.WriteLine row
WScript.StdOut.WriteLine row
row = word(i)
row = word(i)
End If
End If
Next
Next
If Len(row) > 0 Then
If Len(row) > 0 Then
WScript.StdOut.WriteLine row
WScript.StdOut.WriteLine row
End If
End If
End Sub
End Sub
</lang>
</lang>
Line 5,180: Line 5,293:


sub words(w$, p$(), d$)
sub words(w$, p$(), d$)
local n, i, p
local n, i, p
n = split(w$, p$(), d$)
n = split(w$, p$(), d$)
p = 1
p = 1
for i = 1 to n
for i = 1 to n
p$(i) = p$(i) + mid$(w$, p + len(p$(i)), 1)
p$(i) = p$(i) + mid$(w$, p + len(p$(i)), 1)
p = p + len(p$(i))
p = p + len(p$(i))
next i
next i
return n
return n
end sub</lang>
end sub</lang>


Line 5,214: Line 5,327:
length=72, calcIndents=True){
length=72, calcIndents=True){
sink:=Data();
sink:=Data();
getIndents:='wrap(w){ // look at first two lines to indent paragraph
getIndents:='wrap(w){ // look at first two lines to indent paragraph
reg lines=L(), len=0, prefix="", one=True;
reg lines=L(), len=0, prefix="", one=True;
do(2){
do(2){
if(w._next()){
if(w._next()){
lines.append(line:=w.value);
lines.append(line:=w.value);
word:=line.split(Void,1)[0,1]; // get first word, if line !blank
word:=line.split(Void,1)[0,1]; // get first word, if line !blank
if(word){
if(word){
p:=line[0,line.find(word[0]]);
p:=line[0,line.find(word[0]]);
if(one){ sink.write(p); len=p.len(); one=False; }
if(one){ sink.write(p); len=p.len(); one=False; }
else prefix=p;
else prefix=p;
}
}
}
}
}
}
w.push(lines.xplode()); // put first two lines back to be formated
w.push(lines.xplode()); // put first two lines back to be formated
Line 5,231: Line 5,344:
};
};


reg len=0, prefix="", w=text.walker(1); // lines
reg len=0, prefix="", w=text.walker(1); // lines
if(calcIndents) len,prefix=getIndents(w);
if(calcIndents) len,prefix=getIndents(w);
foreach line in (w){
foreach line in (w){
if(not line.strip()){ // blank line
if(not line.strip()){ // blank line
sink.write("\n",line); // blank line redux
sink.write("\n",line); // blank line redux
if(calcIndents) len,prefix=getIndents(w);
if(calcIndents) len,prefix=getIndents(w);
else len=0; // restart formating
else len=0; // restart formating
}else
}else
len=line.split().reduce('wrap(len,word){
len=line.split().reduce('wrap(len,word){
n:=word.len();
n:=word.len();
if(len==0) { sink.write(word); return(n); }
if(len==0) { sink.write(word); return(n); }
nn:=n+1+len; if(nn<=length) { sink.write(" ",word); return(nn); }
nn:=n+1+len; if(nn<=length) { sink.write(" ",word); return(nn); }
sink.write("\n",prefix,word); return(prefix.len()+word.len());
sink.write("\n",prefix,word); return(prefix.len()+word.len());
},len);
},len);
}
}
sink
sink