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