1 /*
2  * OpenClonk, http://www.openclonk.org
3  *
4  * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
5  * Copyright (c) 2013-2016, The OpenClonk Team and contributors
6  *
7  * Distributed under the terms of the ISC license; see accompanying file
8  * "COPYING" for details.
9  *
10  * "Clonk" is a registered trademark of Matthes Bender, used with permission.
11  * See accompanying file "TRADEMARK" for details.
12  *
13  * To redistribute this file separately, substitute the full license texts
14  * for the above references.
15  */
16 // a buffer holding a log history
17 
18 #include "C4Include.h"
19 #include "lib/C4LogBuf.h"
20 
21 #include "graphics/C4FontLoader.h"
22 
C4LogBuffer(int iSize,int iMaxLines,int iLBWidth,const char * szIndentChars,bool fDynamicGrow,bool fMarkup)23 C4LogBuffer::C4LogBuffer(int iSize, int iMaxLines, int iLBWidth, const char *szIndentChars, bool fDynamicGrow, bool fMarkup)
24 		: iBufSize(iSize), iFirstLinePos(0), iAfterLastLinePos(0), iLineDataPos(0),
25 		iNextLineDataPos(0), iMaxLineCount(iMaxLines), iLineCount(0), iLineBreakWidth(iLBWidth), fDynamicGrow(fDynamicGrow), fMarkup(fMarkup)
26 {
27 	// copy indent
28 	if (szIndentChars && *szIndentChars)
29 	{
30 		szIndent = new char[strlen(szIndentChars)+1];
31 		strcpy(szIndent, szIndentChars);
32 	}
33 	else szIndent = nullptr;
34 	// create buffers, if buffer size is given. Otherwise, create/grow them dynamically
35 	if (iBufSize) szBuf = new char[iBufSize]; else szBuf=nullptr;
36 	if (iMaxLineCount) pLineDataBuf = new LineData[iMaxLineCount]; else pLineDataBuf=nullptr;
37 	assert(fDynamicGrow || (iBufSize && iMaxLineCount));
38 }
39 
~C4LogBuffer()40 C4LogBuffer::~C4LogBuffer()
41 {
42 	// free buffers
43 	delete [] pLineDataBuf;
44 	delete [] szBuf;
45 	// free indent
46 	if (szIndent) delete [] szIndent;
47 }
48 
GrowLineCountBuffer(size_t iGrowBy)49 void C4LogBuffer::GrowLineCountBuffer(size_t iGrowBy)
50 {
51 	assert(fDynamicGrow);
52 	if (!iGrowBy) return;
53 	LineData *pNewBuf = new LineData[iMaxLineCount += iGrowBy];
54 	if (iLineCount) memcpy(pNewBuf, pLineDataBuf, sizeof(LineData) * iLineCount);
55 	delete [] pLineDataBuf;
56 	pLineDataBuf = pNewBuf;
57 }
58 
GrowTextBuffer(size_t iGrowBy)59 void C4LogBuffer::GrowTextBuffer(size_t iGrowBy)
60 {
61 	assert(fDynamicGrow);
62 	if (!iGrowBy) return;
63 	char *pNewBuf = new char[iBufSize += iGrowBy];
64 	if (iAfterLastLinePos) memcpy(pNewBuf, szBuf, sizeof(char) * iAfterLastLinePos);
65 	delete [] szBuf;
66 	szBuf = pNewBuf;
67 }
68 
DiscardFirstLine()69 void C4LogBuffer::DiscardFirstLine()
70 {
71 	// any line to discard? - this is guaranteed (private call)
72 	assert(iLineCount && szBuf && !fDynamicGrow);
73 	// dec line count
74 	--iLineCount;
75 	// advance first line pos until delimeter char is reached
76 	while (szBuf[iFirstLinePos]) ++iFirstLinePos;
77 	// skip delimeter
78 	++iFirstLinePos;
79 	// check if end of used buffer is reached (by size or double delimeter)
80 	if (iFirstLinePos == iBufSize || !szBuf[iFirstLinePos] || !iLineCount)
81 	{
82 		// end of buffer reached: wrap to front
83 		iFirstLinePos = 0;
84 	}
85 	// discard line data
86 	++iLineDataPos;
87 	if (!iLineCount) iLineDataPos = iNextLineDataPos = 0;
88 }
89 
AppendSingleLine(const char * szLine,int iLineLength,const char * szIndent,CStdFont * pFont,DWORD dwClr,bool fNewPar)90 void C4LogBuffer::AppendSingleLine(const char *szLine, int iLineLength, const char *szIndent, CStdFont *pFont, DWORD dwClr, bool fNewPar)
91 {
92 	// security: do not append empty line
93 	if (!szLine || !iLineLength || !*szLine) return;
94 	// discard first line or grow buffer if data buffer is full
95 	if (iLineCount == iMaxLineCount)
96 	{
97 		if (fDynamicGrow)
98 			GrowLineCountBuffer(4 + iMaxLineCount/2);
99 		else
100 			DiscardFirstLine();
101 	}
102 	// include trailing zero-character
103 	++iLineLength;
104 	// include indent
105 	if (szIndent) iLineLength += strlen(szIndent);
106 	// but do not add a message that is longer than the buffer (shouldn't happen anyway)
107 	if (iLineLength > iBufSize && !fDynamicGrow)
108 	{
109 		// cut from beginning then
110 		szLine += iLineLength - iBufSize;
111 		iLineLength = iBufSize;
112 	}
113 	// check if the rest of the buffer is sufficient
114 	if (iAfterLastLinePos + iLineLength > iBufSize)
115 	{
116 		if (fDynamicGrow)
117 		{
118 			// insufficient buffer in grow mode: grow text buffer
119 			GrowTextBuffer(std::max(iLineLength, iBufSize/2));
120 		}
121 		else
122 		{
123 			// insufficient buffer in non-grow mode: wrap to beginning
124 			// discard any messages in rest of buffer
125 			// if there are no messages, iFirstLinePos is always zero
126 			// and iAfterLastLinePos cannot be zero here
127 			while (iFirstLinePos >= iAfterLastLinePos) DiscardFirstLine();
128 			// add delimeter to mark end of used buffer
129 			// if the buffer is exactly full by the last line, no delimeter is needed
130 			if (iAfterLastLinePos < iBufSize) szBuf[iAfterLastLinePos] = 0;
131 			// wrap insertion pos to beginning
132 			iAfterLastLinePos = 0;
133 		}
134 	}
135 	// discard any messages within insertion range of new message
136 	if (!fDynamicGrow)
137 		while (iLineCount && Inside(iFirstLinePos, iAfterLastLinePos, iAfterLastLinePos+iLineLength-1))
138 			DiscardFirstLine();
139 	// copy indent
140 	int iIndentLen = 0;
141 	if (szIndent)
142 	{
143 		iIndentLen = strlen(szIndent);
144 		memcpy(szBuf + iAfterLastLinePos, szIndent, iIndentLen);
145 	}
146 	// copy message
147 	if (iLineLength - iIndentLen > 1)
148 		memcpy(szBuf + iAfterLastLinePos + iIndentLen, szLine, iLineLength-iIndentLen-1);
149 	// add delimeter
150 	iAfterLastLinePos += iLineLength;
151 	szBuf[iAfterLastLinePos - 1] = 0;
152 	// no need to add any double delimeters, because this is currently the end of the message list
153 	// also no need to check for end of buffer here, because that will be done when the next message is inserted
154 	// add line data
155 	LineData &rData = pLineDataBuf[iNextLineDataPos];
156 	rData.pFont = pFont;
157 	rData.dwClr = dwClr;
158 	rData.fNewParagraph = fNewPar;
159 	// new message successfully added; count it
160 	++iLineCount;
161 	if (++iNextLineDataPos == iMaxLineCount)
162 	{
163 		if (fDynamicGrow)
164 			GrowLineCountBuffer(4 + iMaxLineCount/2);
165 		else
166 			iNextLineDataPos = 0;
167 	}
168 }
169 
AppendLines(const char * szLine,CStdFont * pFont,DWORD dwClr,CStdFont * pFirstLineFont)170 void C4LogBuffer::AppendLines(const char *szLine, CStdFont *pFont, DWORD dwClr, CStdFont *pFirstLineFont)
171 {
172 	char LineBreakChars [] = { 0x0D, 0x0A, '|' };
173 	int32_t iLineBreakCharCount = 2 + fMarkup;
174 	// safety
175 	if (!szLine) return;
176 	// split '|'/CR/LF-separations first, if there are any
177 	bool fAnyLineBreakChar = false;
178 	for (int i = 0; i < iLineBreakCharCount; ++i)
179 		if (strchr(szLine, LineBreakChars[i]))
180 		{
181 			fAnyLineBreakChar = true;
182 			break;
183 		}
184 	if (fAnyLineBreakChar)
185 	{
186 		char *szBuf = new char[strlen(szLine)+1];
187 		char *szBufPos, *szPos2 = szBuf, *szBufFind;
188 		strcpy(szBuf, szLine);
189 		while ((szBufPos = szPos2))
190 		{
191 			// find first occurance of any line break char
192 			szPos2 = nullptr;
193 			for (int i = 0; i < iLineBreakCharCount; ++i)
194 				if ((szBufFind = strchr(szBufPos, LineBreakChars[i])))
195 					if (!szPos2 || szBufFind < szPos2)
196 						szPos2 = szBufFind;
197 			// split string at linebreak char
198 			if (szPos2) *szPos2++ = '\0';
199 			// output current line if not empty
200 			if (!*szBufPos) continue;
201 			// first line in caption font
202 			if (pFirstLineFont)
203 			{
204 				AppendLines(szBufPos, pFirstLineFont, dwClr);
205 				pFirstLineFont = nullptr;
206 			}
207 			else
208 				AppendLines(szBufPos, pFont, dwClr);
209 		}
210 		delete [] szBuf;
211 		return;
212 	}
213 	// no line breaks desired: Output all in one line
214 	if (!iLineBreakWidth || !pFont)
215 	{
216 		AppendSingleLine(szLine, strlen(szLine), nullptr, pFont, dwClr, true);
217 	}
218 	else
219 	{
220 		C4Markup markup(false);
221 		const char *markupPos = szLine;
222 		std::string rline;
223 		// output broken lines until there are any
224 		int iLineIndex = 0;
225 		while (*szLine)
226 		{
227 			// get line width of this line
228 			int iBreakWdt = iLineBreakWidth;
229 			if (iLineIndex && szIndent)
230 			{
231 				int32_t iIndentWdt, Q;
232 				pFont->GetTextExtent(szIndent, iIndentWdt, Q, true);
233 				iBreakWdt -= iIndentWdt;
234 			}
235 			// get number of characters printable into this line
236 			const char *szNextLine;
237 			int iNumChars = pFont->GetMessageBreak(szLine, &szNextLine, iBreakWdt);
238 			// make sure not to break markup
239 			if (fMarkup)
240 			{
241 				std::string opening = markup.OpeningTags();
242 				while (markupPos < szNextLine)
243 				{
244 					if (*markupPos == '<')
245 					{
246 						if (markup.Read(&markupPos))
247 						{
248 							if (markupPos > szNextLine)
249 							{
250 								// The message break is within a tag.
251 								iNumChars += markupPos - szNextLine + 1;
252 								szNextLine = markupPos;
253 							}
254 							// Read already moved us over a valid tag.
255 							continue;
256 						}
257 					}
258 					markupPos++;
259 				}
260 				std::string closing = markup.ClosingTags();
261 				if (!opening.empty() || !closing.empty())
262 				{
263 					rline = std::move(opening);
264 					rline.append(szLine, iNumChars);
265 					rline.append(closing);
266 					szLine = rline.c_str();
267 					iNumChars = rline.size();
268 				}
269 			}
270 			// add them
271 			AppendSingleLine(szLine, iNumChars, iLineIndex ? szIndent : nullptr, pFont, dwClr, !iLineIndex);
272 			// next line
273 			szLine = szNextLine;
274 			++iLineIndex;
275 			rline.clear();
276 		}
277 	}
278 }
279 
GetLine(int iLineIndex,CStdFont ** ppFont,DWORD * pdwClr,bool * pfNewPar) const280 const char *C4LogBuffer::GetLine(int iLineIndex, CStdFont **ppFont, DWORD *pdwClr, bool *pfNewPar) const
281 {
282 	// evaluate negative indices
283 	if (iLineIndex < 0)
284 	{
285 		iLineIndex += iLineCount;
286 		if (iLineIndex < 0) return nullptr;
287 	}
288 	// range check
289 	if (iLineIndex >= iLineCount) return nullptr;
290 	// assign data
291 	LineData &rData = pLineDataBuf[(iLineDataPos + iLineIndex) % iMaxLineCount];
292 	if (ppFont) *ppFont = rData.pFont;
293 	if (pdwClr) *pdwClr = rData.dwClr;
294 	if (pfNewPar) *pfNewPar = rData.fNewParagraph;
295 	// advance in lines until desired line is found
296 	char *szResult = szBuf + iFirstLinePos;
297 	while (iLineIndex--)
298 	{
299 		// skip this line
300 		while (*szResult++) ;
301 		// double delimeter or end of buffer: reset searching to front of buffer
302 		if (szResult == (szBuf+iBufSize) || !*szResult) szResult = szBuf;
303 	}
304 	// return found buffer pos
305 	return szResult;
306 }
307 
Clear()308 void C4LogBuffer::Clear()
309 {
310 	// clear buffer usage
311 	iFirstLinePos = iAfterLastLinePos = iLineCount = iNextLineDataPos = iLineDataPos = 0;
312 }
313 
SetLBWidth(int iToWidth)314 void C4LogBuffer::SetLBWidth(int iToWidth)
315 {
316 	iLineBreakWidth = iToWidth;
317 }
318