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 |
|||
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> |
}</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 " |
|||
"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 |
#define PENALTY_LONG 100 |
||
#define PENALTY_SHORT |
#define PENALTY_SHORT 1 |
||
typedef struct word_t { |
typedef struct word_t { |
||
const char *s; |
|||
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; |
|||
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 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 |
/* 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 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) |
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 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> |
}</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 ), |
|||
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.", |
|||
io:fwrite( "~s~n~n", [paragraph(Paragraph, 72)] ), |
|||
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 ), |
|||
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 |
MODULE RIVERRUN !Schemes for re-flowing wads of text to a specified line length. |
||
INTEGER BL,BLIMIT,BM |
INTEGER BL,BLIMIT,BM !Fingers for the scratchpad. |
||
PARAMETER (BLIMIT = 222) |
PARAMETER (BLIMIT = 222) !This should be enough for normal widths. |
||
CHARACTER*(BLIMIT) BUMF |
CHARACTER*(BLIMIT) BUMF !The scratchpad, accumulating text. |
||
INTEGER OUTBUMF |
INTEGER OUTBUMF !Output unit number. |
||
DATA OUTBUMF/0/ |
DATA OUTBUMF/0/ !Thus detect inadequate initialisation. |
||
PRIVATE BL,BLIMIT,BM |
PRIVATE BL,BLIMIT,BM !These names are not so unusual |
||
PRIVATE BUMF,OUTBUMF |
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.' ') |
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 |
CHARACTER*(*),INTENT(IN):: TEXT !The bumf. If there must be copy-in, at least there need not be copy back. |
||
INTEGER L |
INTEGER L !The length of the bumf. |
||
L = LEN(TEXT) |
L = LEN(TEXT) !So, what is it? |
||
1 IF (L.LE.0) GO TO 2 |
1 IF (L.LE.0) GO TO 2 !Are we there yet? |
||
IF (ICHAR(TEXT(L:L)).GT.ICHAR(" ")) GO TO 2 |
IF (ICHAR(TEXT(L:L)).GT.ICHAR(" ")) GO TO 2 !Control chars are regarded as spaces also. |
||
L = L - 1 |
L = L - 1 !Step back one. |
||
GO TO 1 |
GO TO 1 !And try again. |
||
2 LSTNB = L |
2 LSTNB = L !The last non-blank, possibly zero. |
||
RETURN |
RETURN !Unsafe to use LSTNB as a variable. |
||
END FUNCTION LSTNB |
END FUNCTION LSTNB !Compilers can bungle it. |
||
SUBROUTINE STARTFLOW(OUT,WIDTH) |
SUBROUTINE STARTFLOW(OUT,WIDTH) !Preparation. |
||
INTEGER OUT |
INTEGER OUT !Output device. |
||
INTEGER WIDTH |
INTEGER WIDTH !Width limit. |
||
OUTBUMF = OUT |
OUTBUMF = OUT !Save these |
||
BM = WIDTH |
BM = WIDTH !So that they don't have to be specified every time. |
||
IF (BM.GT.BLIMIT) STOP "Too wide!" |
IF (BM.GT.BLIMIT) STOP "Too wide!" !Alas, can't show the values BLIMIT and WIDTH. |
||
BL = 0 |
BL = 0 !No text already waiting in BUMF |
||
END SUBROUTINE STARTFLOW!Simple enough. |
END SUBROUTINE STARTFLOW!Simple enough. |
||
SUBROUTINE FLOW(TEXT) |
SUBROUTINE FLOW(TEXT) !Add to the ongoing BUMF. |
||
CHARACTER*(*) TEXT |
CHARACTER*(*) TEXT !The text to append. |
||
INTEGER TL |
INTEGER TL !Its last non-blank. |
||
INTEGER T1,T2 |
INTEGER T1,T2 !Fingers to TEXT. |
||
INTEGER L |
INTEGER L !A length. |
||
IF (OUTBUMF.LT.0) STOP "Call STARTFLOW first!" |
IF (OUTBUMF.LT.0) STOP "Call STARTFLOW first!" !Paranoia. |
||
TL = LSTNB(TEXT) |
TL = LSTNB(TEXT) !No trailing spaces, please. |
||
IF (TL.LE.0) THEN |
IF (TL.LE.0) THEN !A blank (or null) line? |
||
CALL FLUSH |
CALL FLUSH !Thus end the paragraph. |
||
RETURN |
RETURN !Perhaps more text will follow, later. |
||
END IF |
END IF !Curse the (possible) full evaluation of .OR. expressions! |
||
IF (TEXT(1:1).LE." ") CALL FLUSH |
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 |
T1 = 1 !Start at the start, blank or not. |
||
10 IF (BL.GT.0) THEN |
10 IF (BL.GT.0) THEN !If there is text waiting in BUMF, |
||
BL = BL + 1 |
BL = BL + 1 !Then this latest text is to be appended |
||
BUMF(BL:BL) = " " |
BUMF(BL:BL) = " " !After one space. |
||
END IF |
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 |
L = TL - T1 + 1 !Length of text to be placed. |
||
IF (BM - BL .GE. L) THEN |
IF (BM - BL .GE. L) THEN !Sufficient space available? |
||
BUMF(BL + 1:BM + L) = TEXT(T1:TL) |
BUMF(BL + 1:BM + L) = TEXT(T1:TL) !Yes. Copy all the remaining text. |
||
BL = BL + L |
BL = BL + L !Advance the finger. |
||
IF (BL .GE. BM - 1) CALL FLUSH |
IF (BL .GE. BM - 1) CALL FLUSH !If there is no space for an addendum. |
||
RETURN |
RETURN !Done. |
||
END IF |
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 |
L = BM - BL !The number of characters available in BUMF. |
||
T2 = T1 + L |
T2 = T1 + L !Finger the first character beyond the take. |
||
IF (TEXT(T2:T2) .LE. " ") GO TO 12 |
IF (TEXT(T2:T2) .LE. " ") GO TO 12 !A splitter character? Happy chance! |
||
T2 = T2 - 1 |
T2 = T2 - 1 !Thus the last character of TEXT that could be placed in BUMF. |
||
11 IF (TEXT(T2:T2) .GT. " ") THEN |
11 IF (TEXT(T2:T2) .GT. " ") THEN !Are we looking at a space yet? |
||
T2 = T2 - 1 |
T2 = T2 - 1 !No. step back one. |
||
IF (T2 .GT. T1) GO TO 11 |
IF (T2 .GT. T1) GO TO 11 !And try again, if possible. |
||
IF (L .LE. 6) THEN |
IF (L .LE. 6) THEN !No splitter found. For short appendage space, |
||
CALL FLUSH |
CALL FLUSH !Starting a new line gives more scope. |
||
GO TO 10 |
GO TO 10 !At the cost of spaces at the end. |
||
END IF |
END IF !But splitting words is unsavoury too. |
||
T2 = T1 + L - 1 |
T2 = T1 + L - 1 !Alas, no split found. |
||
END IF |
END IF !So the end-of-line will force a split. |
||
L = T2 - T1 + 1 |
L = T2 - T1 + 1 !The length I settle on. |
||
12 BUMF(BL + 1:BL + L) = TEXT(T1:T1 + L - 1) |
12 BUMF(BL + 1:BL + L) = TEXT(T1:T1 + L - 1) !I could add a hyphen at the arbitrary chop... |
||
BL = BL + L |
BL = BL + L !The last placed. |
||
CALL FLUSH |
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 |
T1 = T1 + L !Advance to fresh grist. |
||
13 IF (T1.GT.TL) RETURN |
13 IF (T1.GT.TL) RETURN !Perhaps there is no more. No compound testing, alas. |
||
IF (TEXT(T1:T1).LE." ") THEN |
IF (TEXT(T1:T1).LE." ") THEN !Does a space follow a line split? |
||
T1 = T1 + 1 |
T1 = T1 + 1 !Yes. It would appear as a leading space in the output. |
||
GO TO 13 |
GO TO 13 !But the line split stands in for all that. |
||
END IF |
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 |
RETURN !Nope. |
||
CONTAINS |
CONTAINS !A convenience. |
||
SUBROUTINE FLUSH |
SUBROUTINE FLUSH !Save on repetition. |
||
IF (BL.GT.0) WRITE (OUTBUMF,"(A)") BUMF(1:BL) |
IF (BL.GT.0) WRITE (OUTBUMF,"(A)") BUMF(1:BL) !Roll the bumf, if any. |
||
BL = 0 |
BL = 0 !And be ready for more. |
||
END SUBROUTINE FLUSH |
END SUBROUTINE FLUSH !Thus avoid the verbosity of repeated begin ... end blocks. |
||
END SUBROUTINE FLOW |
END SUBROUTINE FLOW !Invoke with one large blob, or, pieces. |
||
END MODULE RIVERRUN |
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 |
IF (BUMF(1:1).NE."C") GO TO 1 !No comment block yet. |
||
CALL STARTFLOW(MSG,66) |
CALL STARTFLOW(MSG,66) !Found it! |
||
3 CALL FLOW(BUMF) |
3 CALL FLOW(BUMF) !Roll its text. |
||
READ (IN,2) BUMF |
READ (IN,2) BUMF !Grab another line. |
||
IF (BUMF(1:1).EQ."C") GO TO 3 |
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 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: | Line 2,610: | ||
=={{header|Lasso}}== |
=={{header|Lasso}}== |
||
<lang Lasso>define wordwrap( |
<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: | Line 3,617: | ||
foreach($word in $divide){ |
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: | Line 3,699: | ||
foreach ($word in $words) |
foreach ($word in $words) |
||
{ |
{ |
||
if($word.Length + 1 -gt $remaining) |
|||
{ |
{ |
||
$output += "`n$word " |
|||
$remaining = $Width - ($word.Length + 1) |
|||
} |
|||
else |
else |
||
{ |
{ |
||
$output += "$word " |
|||
$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 "+ |
|||
"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 |
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. |
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> |
</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" |
|||
} |
} |
||
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 "&_ |
|||
"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) |
Call wordwrap(text,column) |
||
Sub wordwrap(s,n) |
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 |
End Sub |
||
</lang> |
</lang> |
||
Line 5,180: | Line 5,293: | ||
sub words(w$, p$(), d$) |
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> |
end sub</lang> |
||
Line 5,214: | Line 5,327: | ||
length=72, calcIndents=True){ |
length=72, calcIndents=True){ |
||
sink:=Data(); |
sink:=Data(); |
||
getIndents:='wrap(w){ |
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()){ |
|||
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 |
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); |
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 |
|||
if(calcIndents) len,prefix=getIndents(w); |
|||
else len=0; // restart formating |
|||
}else |
}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 |
sink |