1 //******************************************************************************
2 ///
3 /// @file base/textstreambuffer.cpp
4 ///
5 /// Implementations related to buffered text file output.
6 ///
7 /// @copyright
8 /// @parblock
9 ///
10 /// Persistence of Vision Ray Tracer ('POV-Ray') version 3.8.
11 /// Copyright 1991-2018 Persistence of Vision Raytracer Pty. Ltd.
12 ///
13 /// POV-Ray is free software: you can redistribute it and/or modify
14 /// it under the terms of the GNU Affero General Public License as
15 /// published by the Free Software Foundation, either version 3 of the
16 /// License, or (at your option) any later version.
17 ///
18 /// POV-Ray is distributed in the hope that it will be useful,
19 /// but WITHOUT ANY WARRANTY; without even the implied warranty of
20 /// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21 /// GNU Affero General Public License for more details.
22 ///
23 /// You should have received a copy of the GNU Affero General Public License
24 /// along with this program.  If not, see <http://www.gnu.org/licenses/>.
25 ///
26 /// ----------------------------------------------------------------------------
27 ///
28 /// POV-Ray is based on the popular DKB raytracer version 2.12.
29 /// DKBTrace was originally written by David K. Buck.
30 /// DKBTrace Ver 2.0-2.12 were written by David K. Buck & Aaron A. Collins.
31 ///
32 /// @endparblock
33 ///
34 //******************************************************************************
35 
36 // Unit header file must be the first file included within POV-Ray *.cpp files (pulls in config)
37 #include "base/textstreambuffer.h"
38 
39 // C++ variants of standard C header files
40 #include <cstdarg>
41 #include <cstdio>
42 #include <cstring>
43 
44 // Standard C++ header files
45 #include <algorithm>
46 
47 // POV-Ray base header files
48 #include "base/pov_err.h"
49 #include "base/stringutilities.h"
50 #include "base/types.h"
51 
52 // this must be the last file included
53 #include "base/povdebug.h"
54 
55 namespace pov_base
56 {
57 
TextStreamBuffer(size_t buffersize,unsigned int wrapwidth)58 TextStreamBuffer::TextStreamBuffer(size_t buffersize, unsigned int wrapwidth)
59 {
60     boffset = 0;
61     bsize = buffersize;
62     wrap = wrapwidth;
63     curline = 0;
64     buffer = new char[bsize];
65     if (buffer == nullptr)
66         throw POV_EXCEPTION_CODE(kOutOfMemoryErr);
67 }
68 
~TextStreamBuffer()69 TextStreamBuffer::~TextStreamBuffer()
70 {
71     boffset = 0;
72     bsize = 0;
73     wrap = 0;
74     curline = 0;
75     if (buffer != nullptr)
76         delete[] buffer;
77     buffer = nullptr;
78 }
79 
printf(const char * format,...)80 void TextStreamBuffer::printf(const char *format, ...)
81 {
82     va_list marker;
83 
84     va_start(marker, format);
85     std::vsnprintf(&buffer[boffset], bsize - boffset, format, marker);
86     va_end(marker);
87 
88     // direct output
89     directflush(&buffer[boffset], strlen(&buffer[boffset]));
90 
91     boffset = strlen(buffer);
92 
93     // line buffered output
94     lineflush();
95 }
96 
print(const char * str)97 void TextStreamBuffer::print(const char *str)
98 {
99     printf("%s", str);
100 }
101 
puts(const char * str)102 void TextStreamBuffer::puts(const char *str)
103 {
104     printf("%s\n", str);
105 }
106 
putc(int chr)107 void TextStreamBuffer::putc(int chr)
108 {
109     printf("%c", chr);
110 }
111 
printfile(const char * filename,POV_OFF_T offset,POV_LONG lines)112 void TextStreamBuffer::printfile(const char *filename, POV_OFF_T offset, POV_LONG lines)
113 {
114     FILE *file = fopen(filename, "r");
115 
116     if (file != nullptr)
117     {
118         fseek(file, offset, SEEK_SET);
119         printfile(file, lines);
120         fclose(file);
121     }
122 }
123 
printfile(FILE * file,POV_LONG lines)124 void TextStreamBuffer::printfile(FILE *file, POV_LONG lines)
125 {
126     if (file != nullptr)
127     {
128         bool stopposset = (lines < 0); // only if walking backwards stop at current position
129         POV_OFF_T stoppos = (POV_OFF_T)(ftell(file));
130         int chr = 0;
131 
132         if(lines < 0)
133         {
134             POV_LONG lineoffset = lines;
135 
136             // NOTE: This will walk back one line too far! However, it only walks
137             // back to the end of that line. Thus, the next step will walk forward
138             // again to the beginning of the right line, which is the desired
139             // position. Do not change this behavior without testing! [trf]
140             for(POV_OFF_T pos = (POV_OFF_T)(ftell(file)) - 1; (lineoffset < 1) && (pos >= 0); pos--)
141             {
142                 // WARNING: Expensive way to walk backward through a file, but will only
143                 // be used when problems are encountered anyway, and then it most likely
144                 // does not matter if the output of the message takes a tiny bit longer!
145                 fseek(file, pos, SEEK_SET);
146 
147                 chr = fgetc(file);
148 
149                 if((chr == 10) || (chr == 13))
150                 {
151                     chr = fgetc(file);
152                     if(!((chr == 10) || (chr == 13)))
153                         ungetc(chr, file);
154                     lineoffset++;
155                 }
156                 else if(chr == EOF)
157                     break;
158             }
159 
160             // beginning of file was previously reached
161             if(lineoffset < 1)
162                 fseek(file, 0, SEEK_SET);
163 
164             while(lineoffset > 0)
165             {
166                 chr = fgetc(file);
167 
168                 if((chr == 10) || (chr == 13))
169                 {
170                     chr = fgetc(file);
171                     if(!((chr == 10) || (chr == 13)))
172                         ungetc(chr, file);
173                     lineoffset--;
174                 }
175                 else if(chr == EOF)
176                     break;
177             }
178 
179             // make number of lines to output positive for next step
180             lines = -lines;
181         }
182 
183         while(lines > 0)
184         {
185             chr = fgetc(file);
186 
187             if((stopposset == true) && (stoppos == ((POV_OFF_T)(ftell(file)) - 1))) // only if walking backwards stop at initial position
188                 break;
189 
190             // count newlines in file and replace newlines with system specific newline character
191             if((chr == 10) || (chr == 13))
192             {
193                 chr = fgetc(file);
194                 if(!((chr == 10) || (chr == 13)))
195                     ungetc(chr, file);
196                 else
197                 {
198                     if((stopposset == true) && (stoppos == ((POV_OFF_T)(ftell(file)) - 1))) // only if walking backwards stop at initial position
199                         break;
200                 }
201                 printf("\n");
202                 lines--;
203             }
204             else if(chr == EOF)
205                 break;
206             else
207                 printf("%c", chr);
208         }
209     }
210 }
211 
flush()212 void TextStreamBuffer::flush()
213 {
214     if(curline > 0)
215         directoutput("\n", 1);
216     curline = 0;
217 
218     lineflush();
219     if(boffset > 0)
220         lineoutput(buffer, boffset);
221     boffset = 0;
222 }
223 
lineoutput(const char * str,unsigned int chars)224 void TextStreamBuffer::lineoutput(const char *str, unsigned int chars)
225 {
226     // by default output to stdout
227     fwrite(str, sizeof(char), chars, stdout);
228     fprintf(stdout, "\n");
229     fflush(stdout);
230 }
231 
directoutput(const char *,unsigned int)232 void TextStreamBuffer::directoutput(const char *, unsigned int)
233 {
234     // does nothing by default
235 }
236 
rawoutput(const char *,unsigned int)237 void TextStreamBuffer::rawoutput(const char *, unsigned int)
238 {
239     // does nothing by default
240 }
241 
lineflush()242 void TextStreamBuffer::lineflush()
243 {
244     unsigned int lasti = 0;
245     unsigned int ii = 0;
246     unsigned int i = 0;
247 
248     // output all complete lines in the buffer
249     while(i < boffset)
250     {
251         if((buffer[i] == '\n') || (buffer[i] == '\r'))
252         {
253             lineoutput(&buffer[lasti], i - lasti);
254             lasti = i + 1;
255         }
256         else if(i - lasti >= wrap)
257         {
258             // track back to last space up to 1/4 in the line to wrap
259             for(ii = 0; ii < min((wrap / 4), i); ii++)
260             {
261                 if(isspace(buffer[i - ii]))
262                     break;
263             }
264 
265             // if no space was found in the last 1/4 of the line to wrap, split it at the end anyway
266             if(ii == min((wrap / 4), i))
267                 ii = 0;
268             i -= ii;
269 
270             lineoutput(&buffer[lasti], i - lasti);
271             lasti = i;
272             continue;
273         }
274         i++;
275     }
276 
277     if(lasti > 0)
278     {
279         // remove all completely output lines
280         boffset -= lasti;
281         memmove(buffer, &buffer[lasti], boffset);
282     }
283 }
284 
directflush(const char * str,unsigned int chars)285 void TextStreamBuffer::directflush(const char *str, unsigned int chars)
286 {
287     unsigned int ii = 0;
288     unsigned int i = 0;
289 
290     rawoutput(str, chars);
291 
292     for(i = 0; i < chars; i++)
293     {
294         if((str[i] == '\n') || (str[i] == '\r'))
295         {
296             i++;
297             directoutput(str, i);
298             str += i;
299             chars -= i;
300             i = 0;
301             curline = 0;
302         }
303         else if(curline + i >= wrap)
304         {
305             // track back to last space up to 1/4 in the line to wrap
306             for(ii = 0; ii < min((wrap / 4), i); ii++)
307             {
308                 if(isspace(str[i - ii]))
309                     break;
310             }
311 
312             // if no space was found in the last 1/4 of the line to wrap, split it at the end anyway
313             if(ii == min((wrap / 4), i))
314                 ii = 0;
315             i -= ii;
316 
317             directoutput(str, i);
318             directoutput("\n", 1);
319 
320             str += i;
321             chars -= i;
322             i = 0;
323             curline = 0;
324         }
325     }
326 
327     if(chars > 0)
328     {
329         directoutput(str, chars);
330         curline += chars;
331     }
332 }
333 
334 }
335