Mon May 6 09:51:05 1996 Roland McGrath <roland@delasyd.gnu.ai.mit.edu>
[kopensolaris-gnu/glibc.git] / stdio / linewrap.c
1 /* Word-wrapping and line-truncating streams.
2 Copyright (C) 1996 Free Software Foundation, Inc.
3 This file is part of the GNU C Library.
4
5 The GNU C Library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Library General Public License as
7 published by the Free Software Foundation; either version 2 of the
8 License, or (at your option) any later version.
9
10 The GNU C Library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 Library General Public License for more details.
14
15 You should have received a copy of the GNU Library General Public
16 License along with the GNU C Library; see the file COPYING.LIB.  If
17 not, write to the Free Software Foundation, Inc., 675 Mass Ave,
18 Cambridge, MA 02139, USA.  */
19
20 #include <stdio.h>
21 #include <ctype.h>
22 #include <string.h>
23 #include <stdlib.h>
24
25 /* We keep this data for each line-wrapping stream.  */
26
27 struct data
28   {
29     const size_t *lmargin, *rmargin; /* Left and right margins.  */
30     const size_t *wrapmargin;   /* Margin to wrap to, or null to truncate.  */
31     size_t point;               /* Current column of last chars flushed.  */
32
33     /* Original cookie and hooks from the stream.  */
34     void *cookie;
35     void (*output) (FILE *, int);
36     __io_close_fn *close;
37     __io_fileno_fn *fileno;
38     __io_seek_fn *seek;
39   };
40
41 /* Install our hooks into a stream.  */
42
43 static inline void
44 wrap_stream (FILE *stream, struct data *d)
45 {
46   static void lwoutput (FILE *, int);
47   static __io_close_fn lwclose;
48   static __io_fileno_fn lwfileno;
49
50   stream->__cookie = d;
51   stream->__room_funcs.__output = &lwoutput;
52   stream->__io_funcs.__close = &lwclose;
53   stream->__io_funcs.__fileno = &lwfileno;
54   stream->__io_funcs.__seek = NULL; /* Cannot seek.  */
55 }
56
57 /* Restore a stream to its original state.  */
58
59 static inline void
60 unwrap_stream (FILE *stream, struct data *d)
61 {
62   stream->__cookie = d->cookie;
63   stream->__room_funcs.__output = d->output;
64   stream->__io_funcs.__close = d->close;
65   stream->__io_funcs.__fileno = d->fileno;
66   stream->__io_funcs.__seek = d->seek;
67 }
68 \f
69 /* Cookie io functions that might get called on a wrapped stream.
70    Must pass the original cookie to the original functions.  */
71
72 static int
73 lwclose (void *cookie)
74 {
75   struct data *d = cookie;
76   return (*d->close) (d->cookie);
77 }
78
79 static int
80 lwfileno (void *cookie)
81 {
82   struct data *d = cookie;
83   return (*d->fileno) (d->cookie);
84 }
85 \f
86 /* This function is called when STREAM must be flushed.
87    C is EOF or a character to be appended to the buffer contents.  */
88
89 static void
90 lwoutput (FILE *stream, int c)
91 {
92   char *buf, *nl;
93   size_t len;
94
95   /* Extract our data and restore the stream's original cookie
96      and output function so writes we do really go out.  */
97   struct data *d = stream->__cookie;
98   unwrap_stream (stream, d);
99
100   /* Scan the buffer for newlines.  */
101   for (buf = stream->__buffer;
102        (buf < stream->__bufp || (c != EOF && c != '\n')) && !stream->__error)
103     {
104       size_t r;
105
106       if (d->point == 0 && d->lmargin && *d->lmargin != 0)
107         {
108           /* We are starting a new line.  Print spaces to the left margin.  */
109           const size_t pad = *d->lmargin;
110           if (stream->__bufp + pad < stream->__put_limit)
111             {
112               /* We can fit in them in the buffer by moving the
113                  buffer text up and filling in the beginning.  */
114               memmove (buf + pad, buf, stream->__bufp - buf);
115               stream->__bufp += pad; /* Compensate for bigger buffer. */
116               memset (buf, ' ', pad); /* Fill in the spaces.  */
117               buf += pad; /* Don't bother searching them.  */
118             }
119           else
120             {
121               /* No buffer space for spaces.  Must flush.  */
122               size_t i;
123               char *olimit;
124
125               len = stream->__bufp - buf;
126               olimit = stream->__put_limit;
127               stream->__bufp = stream->__put_limit = buf;
128               for (i = 0; i < pad; ++i)
129                 (*d->output) (stream, ' ');
130               stream->__put_limit = olimit;
131               memcpy (stream->__bufp, buf, len);
132               stream->__bufp += len;
133             }
134           d->point = pad;
135         }
136
137       len = stream->__bufp - buf;
138       nl = memchr (buf, '\n', len);
139
140       if (!nl)
141         {
142           /* The buffer ends in a partial line.  */
143
144           if (!d->rmargin ||
145               d->point + len + (c != EOF && c != '\n') <= d->rmargin)
146             {
147               /* The remaining buffer text is a partial line and fits
148                  within the maximum line width.  Advance point for the
149                  characters to be written and stop scanning.  */
150               d->point += len;
151               break;
152             }
153           else
154             /* Set the end-of-line pointer for the code below to
155                the end of the buffer.  */
156             nl = stream->__bufp;
157         }
158       else if (!d->rmargin || d->point + (nl - buf) <= d->rmargin)
159         {
160           /* The buffer contains a full line that fits within the maximum
161              line width.  Reset point and scan the next line.  */
162           d->point = 0;
163           buf = nl + 1;
164           continue;
165         }
166
167       /* This line is too long.  */
168       r = *d->rmargin;
169
170       if (! d->wrapmargin)
171         {
172           /* Truncate the line by overwriting the excess with the
173              newline and anything after it in the buffer.  */
174           if (nl < stream->__bufp)
175             {
176               memcpy (buf + (r - d->point), nl, stream->__bufp - nl);
177               stream->__bufp -= buf + (r - d->point) - nl;
178               /* Reset point for the next line and start scanning it.  */
179               d->point = 0;
180               buf += r + 1; /* Skip full line plus \n. */
181             }
182           else
183             {
184               /* The buffer ends with a partial line that is beyond the
185                  maximum line width.  Advance point for the characters
186                  written, and discard those past the max from the buffer.  */
187               d->point += len;
188               stream->__bufp -= d->point - r;
189               if (c != '\n')
190                 /* Swallow the extra character too.  */
191                 c = EOF;
192               break;
193             }
194         }
195       else
196         {
197           /* Do word wrap.  Go to the column just past the maximum line
198              width and scan back for the beginning of the word there.
199              Then insert a line break.  */
200
201           char *p, *nextline;
202           int i;
203
204           p = buf + (r + 1 - d->point);
205           while (p >= buf && !isblank (*p))
206             --p;
207           nextline = p + 1;     /* This will begin the next line.  */
208
209           if (nextline > buf)
210             {
211               /* Swallow separating blanks.  */
212               do
213                 --p;
214               while (isblank (*p));
215               nl = p + 1;       /* The newline will replace the first blank. */
216             }
217           else
218             {
219               /* A single word that is greater than the maximum line width.
220                  Oh well.  Put it on an overlong line by itself.  */
221               p = buf + (r + 1 - d->point);
222               /* Find the end of the long word.  */
223               do
224                 ++p;
225               while (p < nl && !isblank (*p));
226               if (p == nl)
227                 {
228                   /* It already ends a line.  No fussing required.  */
229                   d->point = 0;
230                   buf = nl + 1;
231                   continue;
232                 }
233               /* We will move the newline to replace the first blank.  */
234               nl = p;
235               /* Swallow separating blanks.  */
236               do
237                 ++p;
238               while (isblank (*p));
239               /* The next line will start here.  */
240               nextline = p;
241             }
242
243           /* Temporarily reset bufp to include just the first line.  */
244           stream->__bufp = nl;
245           if (nextline - (nl + 1) < d->wrap)
246             /* The margin needs more blanks than we removed.
247                Output the first line so we can use the space.  */
248             (*d->output) (stream, '\n');
249           else
250             /* We can fit the newline and blanks in before
251                the next word.  */
252             *stream->__bufp++ = '\n';
253
254           /* Reset the counter of what has been output this line.  */
255           d->point = 0;
256
257           /* Add blanks up to the wrap margin column.  */
258           for (i = 0; i < d->wrap; ++i)
259             *stream->__bufp++ = ' ';
260
261           /* Copy the tail of the original buffer into the current buffer
262              position.  */
263           if (stream->__bufp != nextline)
264             memcpy (stream->__bufp, nextline, buf + len - nextline);
265           len -= nextline - buf;
266
267           /* Continue the scan on the remaining lines in the buffer.  */
268           buf = stream->__bufp;
269
270           /* Restore bufp to include all the remaining text.  */
271           stream->__bufp += len;
272         }
273     }
274
275   if (!stream->__error)
276     {
277       (*d->output) (stream, c);
278       if (c == '\n')
279         d->point = 0;
280       else if (c != EOF)
281         ++d->point;
282     }
283
284   wrap_stream (stream, d);
285 }
286 \f
287 /* Modify STREAM so that it prefixes lines written on it with *LMARGIN
288    spaces and limits them to *RMARGIN columns total.  If WRAP is not null,
289    words that extend past *RMARGIN are wrapped by replacing the whitespace
290    before them with a newline and *WRAP spaces.  Otherwise, chars beyond
291    *RMARGIN are simply dropped until a newline.  Returns STREAM after
292    modifying it, or NULL if there was an error.  The pointers passed are
293    stored in the stream and so must remain valid until `line_unwrap_stream'
294    is called; the values pointed to can be changed between stdio calls.  */
295
296 FILE *
297 line_wrap_stream (FILE *stream, size_t *lmargin, size_t *rmargin, size_t *wrap)
298 {
299   struct data *d = malloc (sizeof *d);
300
301   if (!d)
302     return NULL;
303
304   /* Ensure full setup before we start tweaking.  */
305   fflush (stream);
306
307   /* Initialize our wrapping state.  */
308   d->point = 0;
309
310   /* Save the original cookie and output and close hooks.  */
311   d->cookie = stream->__cookie;
312   d->output = stream->__room_funcs.__output;
313   d->close = stream->__io_funcs.__close;
314   d->fileno = stream->__io_funcs.__fileno;
315
316   /* Take over the stream.  */
317   wrap_stream (stream, d);
318
319   /* Line-wrapping streams are normally line-buffered.  This is not
320      required, just assumed desired.  The wrapping feature should continue
321      to work if the stream is switched to full or no buffering.  */
322   stream->__linebuf = 1;
323
324 #define ref(arg)        d->arg = arg
325   ref (lmargin);
326   ref (rmargin);
327   ref (wrap);
328 #undef  ref
329
330   return stream;
331 }
332
333 /* Remove the hooks placed in STREAM by `line_wrap_stream'.  */
334
335 void
336 line_unwrap_stream (FILE *stream)
337 {
338   struct data *d = stream->__cookie;
339   unwrap_stream (stream, d);
340   free (d);
341 }
342 \f
343 #ifdef TEST
344 int
345 main (int argc, char **argv)
346 {
347   int c;
348   puts ("stopme");
349   line_wrap_stream (stdout, atoi (argv[1]), atoi (argv[2] ?: "-1"));
350   while ((c = getchar()) != EOF) putchar (c);
351   return 0;
352 }
353 #endif