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 , 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