1 /*
2  * Routines to wrap lines and indent them.
3  *
4  * Copyright © 1998-2016 World Wide Web Consortium
5  * See http://www.w3.org/Consortium/Legal/copyright-software
6  *
7  * To do: count characters, not bytes
8  *
9  * Bert Bos
10  * Created 10 May 1998
11  * $Id: textwrap.c,v 1.34 2019/10/05 17:49:44 bbos Exp $
12  */
13 #include "config.h"
14 #include <stdio.h>
15 #include <ctype.h>
16 #include <assert.h>
17 #if STDC_HEADERS
18 # include <string.h>
19 #else
20 # ifndef HAVE_STRCHR
21 #  define strchr index
22 #  define strrchr rindex
23 # endif
24 #endif
25 #include <stdlib.h>
26 #include <stdbool.h>
27 #include "export.h"
28 #include "types.e"
29 #include "errexit.e"
30 #include "heap.e"
31 
32 /* To do: XML 1.1 allows &#1;, so the following isn't safe anymore */
33 #define NBSP 1					/* Marks non-break-space */
34 #define BREAKOP 2		/* '\002' marks a line break opportunity */
35 
36 static unsigned char *buf = NULL;
37 static int buflen = 0;				/* Size of buf */
38 static int len = 0;				/* Length of buf */
39 static int bufchars = 0;			/* Ditto in chars, not bytes */
40 static int linelen = 0;				/* Length of printed line */
41 static int level = 0;				/* Indentation level */
42 static int indent = 2;				/* # of spaces per indent */
43 static int maxlinelen = 72;			/* Desired line length */
44 static unsigned char prev = NBSP;      		/* Previously added char */
45 
46 /* set_indent -- set the amount of indent per level */
set_indent(int n)47 EXPORT void set_indent(int n) {indent = n;}
48 
49 /* set_linelen -- set the maximum length of a line */
set_linelen(int n)50 EXPORT void set_linelen(int n) {maxlinelen = n;}
51 
52 /* flush -- print word in buf */
flush()53 EXPORT void flush()
54 {
55   int i, j, k;
56 
57   assert(len <= buflen);
58   while (len != 0 && linelen + bufchars >= maxlinelen) { /* Need break */
59     /* Find last breakpoint i before maxlinelen, or first after it */
60     for (i = -1, j = 0, k = 0; j < len && (k < maxlinelen || i == -1); j++)
61       if (buf[j] == ' ') {i = j; k++;}
62       else if (buf[j] == BREAKOP) i = j; /* Breakpoint but no character */
63       else if ((buf[j] & 0xC0) != 0x80) k++; /* Start of a UTF-8 sequence */
64     if (i < 0) break;				/* No breakpoint */
65     assert(i >= 0);				/* Found a breakpoint at i */
66     assert(buf[i] == ' ' || buf[i] == BREAKOP);
67     /* Print up to breakpoint (removing non-break-space markers) */
68     for (j = 0; j < i; j++)
69       if (buf[j] != BREAKOP) {
70 	putchar(buf[j] == NBSP ? ' ' : buf[j]);
71 	if ((buf[j] & 0xC0) != 0x80) bufchars--;
72       }
73     putchar('\n');				/* Break line */
74     linelen = 0;
75     assert(level >= 0);
76     assert(len >= 0);
77     assert(i <= len);
78     i++;					/* Skip the breakpoint */
79     len -= i;
80     if (len != 0) {		/* If anything left, insert the indent */
81       memmove(buf + level * indent, buf + i, len);
82       for (j = 0; j < level * indent; j++) buf[j] = NBSP; /* Indent */
83       len += level * indent;
84       bufchars += level * indent;
85     }
86   }
87   /* Print rest, if any (removing non-break-space markers) */
88   /* First remove spaces at end of line */
89   while (len > 0 && buf[len-1] == ' ') {len--; bufchars--;}
90   for (j = 0; j < len; j++)
91     if (buf[j] == BREAKOP) /* skip */;
92     else if (buf[j] == '\n' || buf[j] == '\r') {putchar(buf[j]); linelen = 0;}
93     else if (buf[j] == NBSP) {putchar(' '); linelen++;}
94     else if ((buf[j] & 0xC0) != 0x80) {putchar(buf[j]); linelen++;}
95     else putchar(buf[j]);
96   bufchars = 0;
97   len = 0;
98 }
99 
100 /* outc -- add one character to output buffer */
outc(char c,bool preformatted,bool with_space)101 EXPORT void outc(char c, bool preformatted, bool with_space)
102 {
103   if (c == '\n' || c == '\r' || c == '\f') {
104     if (preformatted) ;		  /* Keep unchanged */
105     else if (with_space) c = ' '; /* Treated as space */
106     else c = BREAKOP;		  /* Treated as a break opportunity */
107   } else if (c == '\t') {
108     if (preformatted) ;		  /* Keep unchanged */
109     else c = ' ';		  /* Tab is just a space */
110   }
111   if (c == ' ') {
112     if (preformatted) c = NBSP;	  /* Non-break-space marker */
113     else if (prev == ' ') return; /* Don't add another space */
114     else if (prev == BREAKOP) return; /* Don't add a space after \n or similar */
115   }
116   if ((c == ' ' || c == BREAKOP) && linelen + bufchars >= maxlinelen) flush();
117   if (c == '\n' || c == '\r' || c == '\f') flush(); /* Empty the buf */
118   if (c == ' ' && linelen + len == 0) return;	/* No insert at BOL */
119   while (level * indent >= buflen) {buflen += 1024; renewarray(buf, buflen);}
120   if (linelen + len == 0 && !preformatted)
121     while (len < level * indent) {buf[len++] = NBSP; bufchars++;}
122   if (c == ' ' && len && buf[len-1] == ' ') return; /* Skip multiple spaces */
123   while (len >= buflen) {buflen += 1024; renewarray(buf, buflen);}
124     if ((c & 0xC0) != 0x80) bufchars++; /* Character */
125   buf[len++] = c;				/* Finally, insert c */
126   prev = c;					/* Remember for next round */
127 }
128 
129 /* out -- add text to current output line, print line if getting too long */
out(string s,bool preformatted,bool with_space)130 EXPORT void out(string s, bool preformatted, bool with_space)
131 {
132   if (s) for (; *s; s++) outc(*s, preformatted, with_space);
133 }
134 
135 /* outn -- add n chars to current output, print line if getting too long */
outn(string s,size_t n,bool preformatted,bool with_space)136 EXPORT void outn(string s, size_t n, bool preformatted, bool with_space)
137 {
138   size_t i;
139   for (i = 0; i < n; i++) outc(s[i], preformatted, with_space);
140 }
141 
142 /* outln -- add string to output buffer, followed by '\n' */
outln(char * s,bool preformatted,bool with_space)143 EXPORT void outln(char *s, bool preformatted, bool with_space)
144 {
145   out(s, preformatted, with_space);
146   flush();
147   assert(len == 0);
148   assert(bufchars == 0);
149   putchar('\n');
150   linelen = 0;
151 }
152 
153 /* outbreak -- conditional new line; make sure next text starts on new line */
outbreak(void)154 EXPORT void outbreak(void)
155 {
156   flush();
157   assert(len == 0);
158   assert(bufchars == 0);
159   if (linelen != 0) {
160     putchar('\n');
161     linelen = 0;
162   }
163 }
164 
165 /* outbreakpoint -- mark a possible line break point */
outbreakpoint(void)166 EXPORT void outbreakpoint(void)
167 {
168   outc(BREAKOP, false, true);
169 }
170 
171 
172 /* inc_indent -- increase indentation level by 1 */
inc_indent(void)173 EXPORT void inc_indent(void)
174 {
175   flush();
176   level++;
177 }
178 
179 /* dec_indent -- decrease indentation level by 1 */
dec_indent(void)180 EXPORT void dec_indent(void)
181 {
182   flush();
183   level--;
184 }
185