479 lines
12 KiB
C++
479 lines
12 KiB
C++
|
/**
|
||
|
\file G3D.lib/source/TextOutput.cpp
|
||
|
|
||
|
\maintainer Morgan McGuire, http://graphics.cs.williams.edu
|
||
|
\created 2004-06-21
|
||
|
\edited 2013-04-09
|
||
|
|
||
|
Copyright 2000-2013, Morgan McGuire.
|
||
|
All rights reserved.
|
||
|
*/
|
||
|
|
||
|
#include "G3D/TextOutput.h"
|
||
|
#include "G3D/Log.h"
|
||
|
#include "G3D/fileutils.h"
|
||
|
#include "G3D/FileSystem.h"
|
||
|
|
||
|
namespace G3D {
|
||
|
|
||
|
TextOutput::TextOutput(const TextOutput::Settings& opt) :
|
||
|
startingNewLine(true),
|
||
|
currentColumn(0),
|
||
|
inDQuote(false),
|
||
|
filename(""),
|
||
|
indentLevel(0),
|
||
|
m_currentLine(0)
|
||
|
{
|
||
|
setOptions(opt);
|
||
|
}
|
||
|
|
||
|
|
||
|
TextOutput::TextOutput(const std::string& fil, const TextOutput::Settings& opt) :
|
||
|
startingNewLine(true),
|
||
|
currentColumn(0),
|
||
|
inDQuote(false),
|
||
|
filename(fil),
|
||
|
indentLevel(0),
|
||
|
m_currentLine(0)
|
||
|
{
|
||
|
|
||
|
setOptions(opt);
|
||
|
}
|
||
|
|
||
|
|
||
|
void TextOutput::setIndentLevel(int i) {
|
||
|
indentLevel = i;
|
||
|
|
||
|
// If there were more pops than pushes, don't let that take us below 0 indent.
|
||
|
// Don't ever indent more than the number of columns.
|
||
|
indentSpaces =
|
||
|
iClamp(option.spacesPerIndent * indentLevel,
|
||
|
0,
|
||
|
option.numColumns - 1);
|
||
|
}
|
||
|
|
||
|
|
||
|
void TextOutput::setOptions(const Settings& _opt) {
|
||
|
option = _opt;
|
||
|
|
||
|
debugAssert(option.numColumns > 1);
|
||
|
|
||
|
setIndentLevel(indentLevel);
|
||
|
|
||
|
newline = (option.newlineStyle == Settings::NEWLINE_WINDOWS) ? "\r\n" : "\n";
|
||
|
}
|
||
|
|
||
|
|
||
|
void TextOutput::pushIndent() {
|
||
|
setIndentLevel(indentLevel + 1);
|
||
|
}
|
||
|
|
||
|
|
||
|
void TextOutput::popIndent() {
|
||
|
setIndentLevel(indentLevel - 1);
|
||
|
}
|
||
|
|
||
|
|
||
|
static std::string escape(const std::string& string) {
|
||
|
std::string result = "";
|
||
|
|
||
|
for (std::string::size_type i = 0; i < string.length(); ++i) {
|
||
|
char c = string.at(i);
|
||
|
switch (c) {
|
||
|
case '\0':
|
||
|
result += "\\0";
|
||
|
break;
|
||
|
|
||
|
case '\r':
|
||
|
result += "\\r";
|
||
|
break;
|
||
|
|
||
|
case '\n':
|
||
|
result += "\\n";
|
||
|
break;
|
||
|
|
||
|
case '\t':
|
||
|
result += "\\t";
|
||
|
break;
|
||
|
|
||
|
case '\\':
|
||
|
result += "\\\\";
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
result += c;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
|
||
|
void TextOutput::writeString(const std::string& string) {
|
||
|
// Never break a line in a string
|
||
|
const Settings::WordWrapMode old = option.wordWrap;
|
||
|
|
||
|
if (! option.allowWordWrapInsideDoubleQuotes) {
|
||
|
option.wordWrap = Settings::WRAP_NONE;
|
||
|
}
|
||
|
// Convert special characters to escape sequences
|
||
|
this->printf("\"%s\"", escape(string).c_str());
|
||
|
option.wordWrap = old;
|
||
|
}
|
||
|
|
||
|
|
||
|
void TextOutput::writeBoolean(bool b) {
|
||
|
this->printf("%s ", b ? option.trueSymbol.c_str() : option.falseSymbol.c_str());
|
||
|
}
|
||
|
|
||
|
void TextOutput::writeNumber(double n) {
|
||
|
this->printf("%g ", n);
|
||
|
}
|
||
|
|
||
|
|
||
|
void TextOutput::writeNumber(int n) {
|
||
|
this->printf("%d ", n);
|
||
|
}
|
||
|
|
||
|
|
||
|
void TextOutput::writeSymbol(const std::string& string) {
|
||
|
if (string.size() > 0) {
|
||
|
this->printf("%s ", string.c_str());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void TextOutput::writeSymbol(char c) {
|
||
|
this->printf("%c ", c);
|
||
|
}
|
||
|
|
||
|
void TextOutput::writeSymbols(
|
||
|
const std::string& a,
|
||
|
const std::string& b,
|
||
|
const std::string& c,
|
||
|
const std::string& d,
|
||
|
const std::string& e,
|
||
|
const std::string& f) {
|
||
|
|
||
|
writeSymbol(a);
|
||
|
writeSymbol(b);
|
||
|
writeSymbol(c);
|
||
|
writeSymbol(d);
|
||
|
writeSymbol(e);
|
||
|
writeSymbol(f);
|
||
|
}
|
||
|
|
||
|
|
||
|
void TextOutput::printf(const std::string formatString, ...) {
|
||
|
va_list argList;
|
||
|
va_start(argList, formatString);
|
||
|
this->vprintf(formatString.c_str(), argList);
|
||
|
va_end(argList);
|
||
|
}
|
||
|
|
||
|
|
||
|
void TextOutput::printf(const char* formatString, ...) {
|
||
|
va_list argList;
|
||
|
va_start(argList, formatString);
|
||
|
this->vprintf(formatString, argList);
|
||
|
va_end(argList);
|
||
|
}
|
||
|
|
||
|
|
||
|
bool TextOutput::deleteSpace() {
|
||
|
if ((currentColumn > 0) && (data.last() == ' ')) {
|
||
|
data.popDiscard();
|
||
|
--currentColumn;
|
||
|
return true;
|
||
|
} else {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void TextOutput::convertNewlines(const std::string& in, std::string& out) {
|
||
|
// TODO: can be significantly optimized in cases where
|
||
|
// single characters are copied in order by walking through
|
||
|
// the array and copying substrings as needed.
|
||
|
|
||
|
if (option.convertNewlines) {
|
||
|
out = "";
|
||
|
for (uint32 i = 0; i < in.size(); ++i) {
|
||
|
if (in[i] == '\n') {
|
||
|
// Unix newline
|
||
|
out += newline;
|
||
|
} else if ((in[i] == '\r') && (i + 1 < in.size()) && (in[i + 1] == '\n')) {
|
||
|
// Windows newline
|
||
|
out += newline;
|
||
|
++i;
|
||
|
} else {
|
||
|
out += in[i];
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
out = in;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void TextOutput::writeNewline() {
|
||
|
for (uint32 i = 0; i < newline.size(); ++i) {
|
||
|
indentAppend(newline[i]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void TextOutput::writeNewlines(int numLines) {
|
||
|
for (int i = 0; i < numLines; ++i) {
|
||
|
writeNewline();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void TextOutput::wordWrapIndentAppend(const std::string& str) {
|
||
|
// TODO: keep track of the last space character we saw so we don't
|
||
|
// have to always search.
|
||
|
|
||
|
if ((option.wordWrap == Settings::WRAP_NONE) ||
|
||
|
(currentColumn + (int)str.size() <= option.numColumns)) {
|
||
|
// No word-wrapping is needed
|
||
|
|
||
|
// Add one character at a time.
|
||
|
// TODO: optimize for strings without newlines to add multiple
|
||
|
// characters.
|
||
|
for (uint32 i = 0; i < str.size(); ++i) {
|
||
|
indentAppend(str[i]);
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Number of columns to wrap against
|
||
|
int cols = option.numColumns - indentSpaces;
|
||
|
|
||
|
// Copy forward until we exceed the column size,
|
||
|
// and then back up and try to insert newlines as needed.
|
||
|
for (uint32 i = 0; i < str.size(); ++i) {
|
||
|
|
||
|
indentAppend(str[i]);
|
||
|
if ((str[i] == '\r') && (i + 1 < str.size()) && (str[i + 1] == '\n')) {
|
||
|
// \r\n, we need to hit the \n to enter word wrapping.
|
||
|
++i;
|
||
|
indentAppend(str[i]);
|
||
|
}
|
||
|
|
||
|
if (currentColumn >= cols) {
|
||
|
debugAssertM(str[i] != '\n' && str[i] != '\r',
|
||
|
"Should never enter word-wrapping on a newline character");
|
||
|
|
||
|
// True when we're allowed to treat a space as a space.
|
||
|
bool unquotedSpace = option.allowWordWrapInsideDoubleQuotes || ! inDQuote;
|
||
|
|
||
|
// Cases:
|
||
|
//
|
||
|
// 1. Currently in a series of spaces that ends with a newline
|
||
|
// strip all spaces and let the newline
|
||
|
// flow through.
|
||
|
//
|
||
|
// 2. Currently in a series of spaces that does not end with a newline
|
||
|
// strip all spaces and replace them with single newline
|
||
|
//
|
||
|
// 3. Not in a series of spaces
|
||
|
// search backwards for a space, then execute case 2.
|
||
|
|
||
|
// Index of most recent space
|
||
|
size_t lastSpace = data.size() - 1;
|
||
|
|
||
|
// How far back we had to look for a space
|
||
|
size_t k = 0;
|
||
|
size_t maxLookBackward = currentColumn - indentSpaces;
|
||
|
|
||
|
// Search backwards (from current character), looking for a space.
|
||
|
while ((k < maxLookBackward) &&
|
||
|
(lastSpace > 0) &&
|
||
|
(! ((data[(int)lastSpace] == ' ') && unquotedSpace))) {
|
||
|
--lastSpace;
|
||
|
++k;
|
||
|
|
||
|
if ((data[(int)lastSpace] == '\"') && !option.allowWordWrapInsideDoubleQuotes) {
|
||
|
unquotedSpace = ! unquotedSpace;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (k == maxLookBackward) {
|
||
|
// We couldn't find a series of spaces
|
||
|
|
||
|
if (option.wordWrap == Settings::WRAP_ALWAYS) {
|
||
|
// Strip the last character we wrote, force a newline,
|
||
|
// and replace the last character;
|
||
|
data.pop();
|
||
|
writeNewline();
|
||
|
indentAppend(str[i]);
|
||
|
} else {
|
||
|
// Must be Settings::WRAP_WITHOUT_BREAKING
|
||
|
//
|
||
|
// Don't write the newline; we'll come back to
|
||
|
// the word wrap code after writing another character
|
||
|
}
|
||
|
} else {
|
||
|
// We found a series of spaces. If they continue
|
||
|
// to the new string, strip spaces off both. Otherwise
|
||
|
// strip spaces from data only and insert a newline.
|
||
|
|
||
|
// Find the start of the spaces. firstSpace is the index of the
|
||
|
// first non-space, looking backwards from lastSpace.
|
||
|
size_t firstSpace = lastSpace;
|
||
|
while ((k < maxLookBackward) &&
|
||
|
(firstSpace > 0) &&
|
||
|
(data[(int)firstSpace] == ' ')) {
|
||
|
--firstSpace;
|
||
|
++k;
|
||
|
}
|
||
|
|
||
|
if (k == maxLookBackward) {
|
||
|
++firstSpace;
|
||
|
}
|
||
|
|
||
|
if (lastSpace == (uint32)data.size() - 1) {
|
||
|
// Spaces continued up to the new string
|
||
|
data.resize(firstSpace + 1);
|
||
|
writeNewline();
|
||
|
|
||
|
// Delete the spaces from the new string
|
||
|
while ((i < str.size() - 1) && (str[i + 1] == ' ')) {
|
||
|
++i;
|
||
|
}
|
||
|
} else {
|
||
|
// Spaces were somewhere in the middle of the old string.
|
||
|
// replace them with a newline.
|
||
|
|
||
|
// Copy over the characters that should be saved
|
||
|
Array<char> temp;
|
||
|
for (size_t j = lastSpace + 1; j < (uint32)data.size(); ++j) {
|
||
|
char c = data[(int)j];
|
||
|
|
||
|
if (c == '\"') {
|
||
|
// Undo changes to quoting (they will be re-done
|
||
|
// when we paste these characters back on).
|
||
|
inDQuote = !inDQuote;
|
||
|
}
|
||
|
temp.append(c);
|
||
|
}
|
||
|
|
||
|
// Remove those characters and replace with a newline.
|
||
|
data.resize(firstSpace + 1);
|
||
|
writeNewline();
|
||
|
|
||
|
// Write them back
|
||
|
for (size_t j = 0; j < (uint32)temp.size(); ++j) {
|
||
|
indentAppend(temp[(int)j]);
|
||
|
}
|
||
|
|
||
|
// We are now free to continue adding from the
|
||
|
// new string, which may or may not begin with spaces.
|
||
|
|
||
|
} // if spaces included new string
|
||
|
} // if hit indent
|
||
|
} // if line exceeded
|
||
|
} // iterate over str
|
||
|
}
|
||
|
|
||
|
|
||
|
void TextOutput::indentAppend(char c) {
|
||
|
|
||
|
if (startingNewLine) {
|
||
|
for (int j = 0; j < indentSpaces; ++j) {
|
||
|
data.push(' ');
|
||
|
}
|
||
|
startingNewLine = false;
|
||
|
currentColumn = indentSpaces;
|
||
|
}
|
||
|
|
||
|
data.push(c);
|
||
|
|
||
|
// Don't increment the column count on return character
|
||
|
// newline is taken care of below.
|
||
|
if (c != '\r') {
|
||
|
++currentColumn;
|
||
|
}
|
||
|
|
||
|
if (c == '\"') {
|
||
|
inDQuote = ! inDQuote;
|
||
|
}
|
||
|
|
||
|
startingNewLine = (c == '\n');
|
||
|
if (startingNewLine) {
|
||
|
currentColumn = 0;
|
||
|
++m_currentLine;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void TextOutput::vprintf(const char* formatString, va_list argPtr) {
|
||
|
const std::string& str = vformat(formatString, argPtr);
|
||
|
|
||
|
std::string clean;
|
||
|
convertNewlines(str, clean);
|
||
|
wordWrapIndentAppend(clean);
|
||
|
}
|
||
|
|
||
|
|
||
|
void TextOutput::commit(bool flush) {
|
||
|
std::string p = filenamePath(filename);
|
||
|
if (! FileSystem::exists(p, false)) {
|
||
|
FileSystem::createDirectory(p);
|
||
|
}
|
||
|
|
||
|
FILE* f = FileSystem::fopen(filename.c_str(), "wb");
|
||
|
debugAssertM(f, "Could not open \"" + filename + "\"");
|
||
|
fwrite(data.getCArray(), 1, data.size(), f);
|
||
|
if (flush) {
|
||
|
fflush(f);
|
||
|
}
|
||
|
FileSystem::fclose(f);
|
||
|
}
|
||
|
|
||
|
|
||
|
void TextOutput::commitString(std::string& out) {
|
||
|
// Null terminate
|
||
|
data.push('\0');
|
||
|
out = data.getCArray();
|
||
|
data.pop();
|
||
|
}
|
||
|
|
||
|
|
||
|
std::string TextOutput::commitString() {
|
||
|
std::string str;
|
||
|
commitString(str);
|
||
|
return str;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
/////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
void serialize(const float& b, TextOutput& to) {
|
||
|
to.writeNumber(b);
|
||
|
}
|
||
|
|
||
|
|
||
|
void serialize(const bool& b, TextOutput& to) {
|
||
|
to.writeSymbol(b ? "true" : "false");
|
||
|
}
|
||
|
|
||
|
|
||
|
void serialize(const int& b, TextOutput& to) {
|
||
|
to.writeNumber(b);
|
||
|
}
|
||
|
|
||
|
|
||
|
void serialize(const uint8& b, TextOutput& to) {
|
||
|
to.writeNumber(b);
|
||
|
}
|
||
|
|
||
|
|
||
|
void serialize(const double& b, TextOutput& to) {
|
||
|
to.writeNumber(b);
|
||
|
}
|
||
|
|
||
|
|
||
|
}
|