openclonk/src/lib/C4LogBuf.cpp

318 lines
9.3 KiB
C++

/*
* OpenClonk, http://www.openclonk.org
*
* Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
* Copyright (c) 2013-2016, The OpenClonk Team and contributors
*
* Distributed under the terms of the ISC license; see accompanying file
* "COPYING" for details.
*
* "Clonk" is a registered trademark of Matthes Bender, used with permission.
* See accompanying file "TRADEMARK" for details.
*
* To redistribute this file separately, substitute the full license texts
* for the above references.
*/
// a buffer holding a log history
#include "C4Include.h"
#include "lib/C4LogBuf.h"
#include "graphics/C4FontLoader.h"
C4LogBuffer::C4LogBuffer(int iSize, int iMaxLines, int iLBWidth, const char *szIndentChars, bool fDynamicGrow, bool fMarkup)
: iBufSize(iSize), iFirstLinePos(0), iAfterLastLinePos(0), iLineDataPos(0),
iNextLineDataPos(0), iMaxLineCount(iMaxLines), iLineCount(0), iLineBreakWidth(iLBWidth), fDynamicGrow(fDynamicGrow), fMarkup(fMarkup)
{
// copy indent
if (szIndentChars && *szIndentChars)
{
szIndent = new char[strlen(szIndentChars)+1];
strcpy(szIndent, szIndentChars);
}
else szIndent = nullptr;
// create buffers, if buffer size is given. Otherwise, create/grow them dynamically
if (iBufSize) szBuf = new char[iBufSize]; else szBuf=nullptr;
if (iMaxLineCount) pLineDataBuf = new LineData[iMaxLineCount]; else pLineDataBuf=nullptr;
assert(fDynamicGrow || (iBufSize && iMaxLineCount));
}
C4LogBuffer::~C4LogBuffer()
{
// free buffers
delete [] pLineDataBuf;
delete [] szBuf;
// free indent
if (szIndent) delete [] szIndent;
}
void C4LogBuffer::GrowLineCountBuffer(size_t iGrowBy)
{
assert(fDynamicGrow);
if (!iGrowBy) return;
LineData *pNewBuf = new LineData[iMaxLineCount += iGrowBy];
if (iLineCount) memcpy(pNewBuf, pLineDataBuf, sizeof(LineData) * iLineCount);
delete [] pLineDataBuf;
pLineDataBuf = pNewBuf;
}
void C4LogBuffer::GrowTextBuffer(size_t iGrowBy)
{
assert(fDynamicGrow);
if (!iGrowBy) return;
char *pNewBuf = new char[iBufSize += iGrowBy];
if (iAfterLastLinePos) memcpy(pNewBuf, szBuf, sizeof(char) * iAfterLastLinePos);
delete [] szBuf;
szBuf = pNewBuf;
}
void C4LogBuffer::DiscardFirstLine()
{
// any line to discard? - this is guaranteed (private call)
assert(iLineCount && szBuf && !fDynamicGrow);
// dec line count
--iLineCount;
// advance first line pos until delimeter char is reached
while (szBuf[iFirstLinePos]) ++iFirstLinePos;
// skip delimeter
++iFirstLinePos;
// check if end of used buffer is reached (by size or double delimeter)
if (iFirstLinePos == iBufSize || !szBuf[iFirstLinePos] || !iLineCount)
{
// end of buffer reached: wrap to front
iFirstLinePos = 0;
}
// discard line data
++iLineDataPos;
if (!iLineCount) iLineDataPos = iNextLineDataPos = 0;
}
void C4LogBuffer::AppendSingleLine(const char *szLine, int iLineLength, const char *szIndent, CStdFont *pFont, DWORD dwClr, bool fNewPar)
{
// security: do not append empty line
if (!szLine || !iLineLength || !*szLine) return;
// discard first line or grow buffer if data buffer is full
if (iLineCount == iMaxLineCount)
{
if (fDynamicGrow)
GrowLineCountBuffer(4 + iMaxLineCount/2);
else
DiscardFirstLine();
}
// include trailing zero-character
++iLineLength;
// include indent
if (szIndent) iLineLength += strlen(szIndent);
// but do not add a message that is longer than the buffer (shouldn't happen anyway)
if (iLineLength > iBufSize && !fDynamicGrow)
{
// cut from beginning then
szLine += iLineLength - iBufSize;
iLineLength = iBufSize;
}
// check if the rest of the buffer is sufficient
if (iAfterLastLinePos + iLineLength > iBufSize)
{
if (fDynamicGrow)
{
// insufficient buffer in grow mode: grow text buffer
GrowTextBuffer(std::max(iLineLength, iBufSize/2));
}
else
{
// insufficient buffer in non-grow mode: wrap to beginning
// discard any messages in rest of buffer
// if there are no messages, iFirstLinePos is always zero
// and iAfterLastLinePos cannot be zero here
while (iFirstLinePos >= iAfterLastLinePos) DiscardFirstLine();
// add delimeter to mark end of used buffer
// if the buffer is exactly full by the last line, no delimeter is needed
if (iAfterLastLinePos < iBufSize) szBuf[iAfterLastLinePos] = 0;
// wrap insertion pos to beginning
iAfterLastLinePos = 0;
}
}
// discard any messages within insertion range of new message
if (!fDynamicGrow)
while (iLineCount && Inside(iFirstLinePos, iAfterLastLinePos, iAfterLastLinePos+iLineLength-1))
DiscardFirstLine();
// copy indent
int iIndentLen = 0;
if (szIndent)
{
iIndentLen = strlen(szIndent);
memcpy(szBuf + iAfterLastLinePos, szIndent, iIndentLen);
}
// copy message
if (iLineLength - iIndentLen > 1)
memcpy(szBuf + iAfterLastLinePos + iIndentLen, szLine, iLineLength-iIndentLen-1);
// add delimeter
iAfterLastLinePos += iLineLength;
szBuf[iAfterLastLinePos - 1] = 0;
// no need to add any double delimeters, because this is currently the end of the message list
// also no need to check for end of buffer here, because that will be done when the next message is inserted
// add line data
LineData &rData = pLineDataBuf[iNextLineDataPos];
rData.pFont = pFont;
rData.dwClr = dwClr;
rData.fNewParagraph = fNewPar;
// new message successfully added; count it
++iLineCount;
if (++iNextLineDataPos == iMaxLineCount)
{
if (fDynamicGrow)
GrowLineCountBuffer(4 + iMaxLineCount/2);
else
iNextLineDataPos = 0;
}
}
void C4LogBuffer::AppendLines(const char *szLine, CStdFont *pFont, DWORD dwClr, CStdFont *pFirstLineFont)
{
char LineBreakChars [] = { 0x0D, 0x0A, '|' };
int32_t iLineBreakCharCount = 2 + fMarkup;
// safety
if (!szLine) return;
// split '|'/CR/LF-separations first, if there are any
bool fAnyLineBreakChar = false;
for (int i = 0; i < iLineBreakCharCount; ++i)
if (strchr(szLine, LineBreakChars[i]))
{
fAnyLineBreakChar = true;
break;
}
if (fAnyLineBreakChar)
{
char *szBuf = new char[strlen(szLine)+1];
char *szBufPos, *szPos2 = szBuf, *szBufFind;
strcpy(szBuf, szLine);
while ((szBufPos = szPos2))
{
// find first occurance of any line break char
szPos2 = nullptr;
for (int i = 0; i < iLineBreakCharCount; ++i)
if ((szBufFind = strchr(szBufPos, LineBreakChars[i])))
if (!szPos2 || szBufFind < szPos2)
szPos2 = szBufFind;
// split string at linebreak char
if (szPos2) *szPos2++ = '\0';
// output current line if not empty
if (!*szBufPos) continue;
// first line in caption font
if (pFirstLineFont)
{
AppendLines(szBufPos, pFirstLineFont, dwClr);
pFirstLineFont = nullptr;
}
else
AppendLines(szBufPos, pFont, dwClr);
}
delete [] szBuf;
return;
}
// no line breaks desired: Output all in one line
if (!iLineBreakWidth || !pFont)
{
AppendSingleLine(szLine, strlen(szLine), nullptr, pFont, dwClr, true);
}
else
{
C4Markup markup(false);
const char *markupPos = szLine;
std::string rline;
// output broken lines until there are any
int iLineIndex = 0;
while (*szLine)
{
// get line width of this line
int iBreakWdt = iLineBreakWidth;
if (iLineIndex && szIndent)
{
int32_t iIndentWdt, Q;
pFont->GetTextExtent(szIndent, iIndentWdt, Q, true);
iBreakWdt -= iIndentWdt;
}
// get number of characters printable into this line
const char *szNextLine;
int iNumChars = pFont->GetMessageBreak(szLine, &szNextLine, iBreakWdt);
// make sure not to break markup
if (fMarkup)
{
std::string opening = markup.OpeningTags();
while (markupPos < szNextLine)
{
if (*markupPos == '<')
{
if (markup.Read(&markupPos))
{
if (markupPos > szNextLine)
{
// The message break is within a tag.
iNumChars += markupPos - szNextLine + 1;
szNextLine = markupPos;
}
// Read already moved us over a valid tag.
continue;
}
}
markupPos++;
}
std::string closing = markup.ClosingTags();
if (!opening.empty() || !closing.empty())
{
rline = std::move(opening);
rline.append(szLine, iNumChars);
rline.append(closing);
szLine = rline.c_str();
iNumChars = rline.size();
}
}
// add them
AppendSingleLine(szLine, iNumChars, iLineIndex ? szIndent : nullptr, pFont, dwClr, !iLineIndex);
// next line
szLine = szNextLine;
++iLineIndex;
rline.clear();
}
}
}
const char *C4LogBuffer::GetLine(int iLineIndex, CStdFont **ppFont, DWORD *pdwClr, bool *pfNewPar) const
{
// evaluate negative indices
if (iLineIndex < 0)
{
iLineIndex += iLineCount;
if (iLineIndex < 0) return nullptr;
}
// range check
if (iLineIndex >= iLineCount) return nullptr;
// assign data
LineData &rData = pLineDataBuf[(iLineDataPos + iLineIndex) % iMaxLineCount];
if (ppFont) *ppFont = rData.pFont;
if (pdwClr) *pdwClr = rData.dwClr;
if (pfNewPar) *pfNewPar = rData.fNewParagraph;
// advance in lines until desired line is found
char *szResult = szBuf + iFirstLinePos;
while (iLineIndex--)
{
// skip this line
while (*szResult++) ;
// double delimeter or end of buffer: reset searching to front of buffer
if (szResult == (szBuf+iBufSize) || !*szResult) szResult = szBuf;
}
// return found buffer pos
return szResult;
}
void C4LogBuffer::Clear()
{
// clear buffer usage
iFirstLinePos = iAfterLastLinePos = iLineCount = iNextLineDataPos = iLineDataPos = 0;
}
void C4LogBuffer::SetLBWidth(int iToWidth)
{
iLineBreakWidth = iToWidth;
}