HPCToolkit
line_wrapping.c
Go to the documentation of this file.
1 /*
2  This code provides a minimal way to do line wrapping in C.
3 
4  strwrap() will take an input string and a desired line width and
5  attempt to split it into suitable chunks.
6 
7  The result will be provided as an array of string pointers, each
8  referring to the position of the first character on a line.
9 
10  It tries to be clever about whitespace and start each line at the
11  beginning of a word (it will not erase whitespace in the middle of
12  lines). Newlines encountered in the string will be converted into
13  dummy lines of 0 width.
14 
15  How to use:
16  - Prepare the text you wish to wrap
17  - Call strwrap()
18  - Print the result line by line
19  - Free the arrays strwrap() returned
20 
21  The code was designed with the following restrictions:
22  - the original string must remain unmodified
23  - everything contained in a single function
24  - no structs or declarations (also helps minimize cleanup)
25 
26  Realistic expectations: There is no support for wide characters. It
27  has no notion of control codes or formatting markup and will treat
28  these just like regular words. It doesn't really care about tabs. It
29  doesn't do anything about line raggedness or hyphenation.
30 
31  This code is meant to be educational and hasn't been overly
32  optimized. I could probably use char pointers instead of array
33  offsets in many places. However, keeping it this way should make
34  it easier to adapt for other languages.
35 
36  main() provides a small test program.
37 
38  Compile with something like: gcc strwrap.c -o strwraptest
39 
40  You may use this code in your program. Please don't distribute
41  modified versions without clearly stating you have changed it.
42 
43  ulf.astrom@gmail.com / happyponyland.net, 2012-11-12
44 */
45 
46 
47 #include <stdio.h>
48 #include <stdlib.h>
49 #include <string.h>
50 #include <ctype.h>
51 
52 
53 /*
54  Splits s into multiple lines of width w. Returns the number of
55  lines. line_ret will be set to an array of string pointers, each
56  pointing to the start of a line. len_ret will be set to an array of
57  ints, describing how wide the corresponding line is. If an error
58  occurs 0 is returned; line_ret and len_ret are then left unmodified.
59 */
60 int strwrap(char * s, int w, char *** line_ret, int ** len_ret)
61 {
62  int allocated; /* lines allocated */
63  int lines; /* lines used */
64  char ** line;
65  int * len;
66  int tl; /* total length of the string */
67  int l; /* length of current line */
68  int p; /* offset (from s) of current line */
69  int close_word;
70  int open_word;
71  int i;
72 
73  if (s == NULL)
74  return 0;
75 
76  tl = strlen(s);
77 
78  if (tl == 0 || w <= 0)
79  return 0;
80 
81  lines = 0;
82 
83  /*
84  Preemptively allocate memory. This should be enough for most uses;
85  if we need more we will realloc later.
86  */
87  allocated = (tl / w) * 1.5 + 1;
88 
89  line = (char **) malloc(sizeof(char *) * allocated);
90  len = (int *) malloc(sizeof(int) * allocated);
91 
92  if (line == NULL || len == NULL)
93  return 0;
94 
95  /*
96  p will be an offset from the start of the string and the start of
97  the current line we are processing.
98  */
99 
100  p = 0;
101 
102  while (p < tl)
103  {
104  /* Detect initial newlines */
105  if (s[p] == '\n')
106  {
107  l = 0;
108  goto make_new_line;
109  }
110 
111  /*
112  Fast-forward past initial whitespace on the current line. You
113  might want to comment this out if you need formatting like
114  " * Bullet point lists" and wish to preserve the spaces.
115  */
116  if (isspace(s[p]))
117  {
118  p++;
119  continue;
120  }
121 
122  /*
123  Decide how long the current line should be. We typically want
124  the line to take up the full allowed line width, but we also
125  want to limit the perceived length of the final line. If the
126  line width overshoots the end of the string, truncate it.
127  */
128  if (p + w > tl)
129  w = tl - p;
130 
131  l = w;
132 
133  /*
134  If the break point ends up within a word, count how many
135  characters of that word fall outside the window to the right.
136  */
137  close_word = 0;
138 
139  while (s[p + l + close_word] != '\0' && !isspace(s[p + l + close_word]))
140  close_word++;
141 
142  /*
143  Now backtrack from the break point until we find some
144  whitespace. Keep track of how many characters we traverse.
145  */
146  open_word = 0;
147 
148  while (s[p + l] != '\0' && !isspace(s[p + l]))
149  {
150  l--;
151  open_word ++;
152 
153  /*
154  If the current word length is near the line width it will be
155  hard to fit it all on a line, so we should just leave as much
156  of it as possible on this line. Remove the fraction if you
157  only want longer words to break.
158  */
159  if (open_word + close_word > w * 0.8)
160  {
161  l = w;
162  break;
163  }
164  }
165 
166  /*
167  We now have a line width we wish to use. Just make a final check
168  there are no newlines in the middle of the line. If there are,
169  break at that point instead.
170  */
171  for (i = 0; i < l; i++)
172  {
173  if (s[p + i] == '\n')
174  {
175  l = i;
176  break;
177  }
178  }
179 
180  make_new_line:
181  /*
182  We have decided how long this line should be. Check that we have
183  enough memory reserved for the line pointers; allocate more if
184  needed.
185  */
186 
187  line[lines] = &s[p];
188  len[lines] = l;
189  lines++;
190 
191  if (lines >= allocated)
192  {
193  allocated *= 2;
194 
195  line = (char **) realloc(line, sizeof(char *) * allocated);
196  len = (int *) realloc(len, sizeof(int) * allocated);
197 
198  if (line == NULL || len == NULL)
199  return 0;
200  }
201 
202  /*
203  Move on to the next line. This needs to be 1 less than the
204  desired width or we will drop characters in the middle of
205  really long words.
206  */
207  if (l == w)
208  l--;
209 
210  p += l + 1;
211  }
212 
213  /* Finally, relinquish memory we don't need */
214  line = (char **) realloc(line, sizeof(char *) * lines);
215  len = (int *) realloc(len, sizeof(int) * lines);
216 
217  *line_ret = line;
218  *len_ret = len;
219 
220  return lines;
221 }
222 
223 
224 #if DBG_LINE_WRAPPING
225 
226 /*
227  Test program for strwrap.
228 */
229 int main()
230 {
231  char test[700];
232  char shit[100];
233  char ** line;
234  int * len;
235  int lines;
236  int i;
237  int w;
238 
239  /* Prepare the string we wish to wrap */
240  strcpy(test, "Hey this is a test ok testing some more let's see where this line breaks just a couple words more andareallylongwordthatshouldbreaksomewhereinhalf to see it's really working and now let's see how it works with lots of whitespace and a couple of\nnewlines and also\n\ndouble newlines in different arrangements \n\n andanotherreallylongwordrightafteranewlinesowecanseeit'sworkingallright and now let's see how it aligns with the full line width\naaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa and line width + 1\n aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab yeah it seems pretty ok now the last line should be 10 wide\nok thx bye");
241 
242  /* The width of the destination "box" */
243  w = 33;
244 
245  /* Call strwrap like this to wrap the string */
246  lines = strwrap(test, w, &line, &len);
247 
248  /*
249  Print the result. We'll print the length of each line we received
250  and pad each line to w width.
251  */
252  printf("Got %d lines:\n\n", lines);
253 
254  for (i = 0; i < lines; i++)
255  {
256  strncpy(shit, line[i], len[i]);
257  shit[len[i]] = '\0';
258  printf("%4d |%-*s|\n", len[i], w, shit);
259  }
260 
261  /* This is the only cleanup needed. */
262  free(line);
263  free(len);
264 
265  return 0;
266 }
267 #endif // DBG_LINE_WRAPPING
int strwrap(char *s, int w, char ***line_ret, int **len_ret)
Definition: line_wrapping.c:60
void MONITOR_EXT_WRAP_NAME() free(void *ptr)
int main(int argc, char *argv[])
Definition: main.cpp:125
void *MONITOR_EXT_WRAP_NAME() realloc(void *ptr, size_t bytes)
void *MONITOR_EXT_WRAP_NAME() malloc(size_t bytes)
#define NULL
Definition: ElfHelper.cpp:85